swc_ecma_preset_env/
lib.rs

1#![deny(clippy::all)]
2#![allow(dead_code)]
3#![recursion_limit = "256"]
4
5use std::{path::PathBuf, sync::Arc};
6
7use preset_env_base::query::targets_to_versions;
8pub use preset_env_base::{query::Targets, version::Version, BrowserData, Versions};
9use rustc_hash::FxHashSet;
10use serde::Deserialize;
11use swc_atoms::{atom, Atom};
12use swc_common::{comments::Comments, pass::Optional, FromVariant, Mark, SyntaxContext, DUMMY_SP};
13use swc_ecma_ast::*;
14use swc_ecma_transforms::{
15    compat::{
16        bugfixes,
17        class_fields_use_set::class_fields_use_set,
18        es2015::{self, generator::generator},
19        es2016, es2017, es2018, es2019, es2020, es2021, es2022, es3,
20        regexp::{self, regexp},
21    },
22    feature::FeatureFlag,
23    Assumptions,
24};
25use swc_ecma_utils::{prepend_stmts, ExprFactory};
26use swc_ecma_visit::{visit_mut_pass, VisitMut, VisitMutWith, VisitWith};
27
28pub use self::transform_data::Feature;
29
30#[macro_use]
31mod util;
32mod corejs2;
33mod corejs3;
34mod regenerator;
35mod transform_data;
36
37pub fn preset_env<C>(
38    unresolved_mark: Mark,
39    comments: Option<C>,
40    c: Config,
41    assumptions: Assumptions,
42    feature_set: &mut FeatureFlag,
43) -> impl Pass
44where
45    C: Comments + Clone,
46{
47    let loose = c.loose;
48    let targets = targets_to_versions(c.targets).expect("failed to parse targets");
49    let is_any_target = targets.is_any_target();
50
51    let (include, included_modules) = FeatureOrModule::split(c.include);
52    let (exclude, excluded_modules) = FeatureOrModule::split(c.exclude);
53
54    let pass = noop_pass();
55
56    macro_rules! should_enable {
57        ($feature:ident, $default:expr) => {{
58            let f = transform_data::Feature::$feature;
59            !exclude.contains(&f)
60                && (c.force_all_transforms
61                    || (is_any_target
62                        || include.contains(&f)
63                        || f.should_enable(&targets, c.bugfixes, $default)))
64        }};
65    }
66
67    macro_rules! add {
68        ($prev:expr, $feature:ident, $pass:expr) => {{
69            add!($prev, $feature, $pass, false)
70        }};
71        ($prev:expr, $feature:ident, $pass:expr, $default:expr) => {{
72            let f = transform_data::Feature::$feature;
73
74            let enable = should_enable!($feature, $default);
75
76            if !enable {
77                *feature_set |= swc_ecma_transforms::feature::FeatureFlag::$feature;
78            }
79
80            if c.debug {
81                println!("{}: {:?}", f.as_str(), enable);
82            }
83            ($prev, Optional::new($pass, enable))
84        }};
85    }
86
87    let pass = (
88        pass,
89        Optional::new(
90            class_fields_use_set(assumptions.pure_getters),
91            assumptions.set_public_class_fields,
92        ),
93    );
94
95    let pass = {
96        let enable_dot_all_regex = should_enable!(DotAllRegex, false);
97        let enable_named_capturing_groups_regex = should_enable!(NamedCapturingGroupsRegex, false);
98        let enable_sticky_regex = should_enable!(StickyRegex, false);
99        let enable_unicode_property_regex = should_enable!(UnicodePropertyRegex, false);
100        let enable_unicode_regex = should_enable!(UnicodeRegex, false);
101        let enable_unicode_sets_regex = should_enable!(UnicodeSetsRegex, false);
102
103        let enable = enable_dot_all_regex
104            || enable_named_capturing_groups_regex
105            || enable_sticky_regex
106            || enable_unicode_property_regex
107            || enable_unicode_regex;
108
109        (
110            pass,
111            Optional::new(
112                regexp(regexp::Config {
113                    dot_all_regex: enable_dot_all_regex,
114                    // TODO: add Feature:HasIndicesRegex
115                    has_indices: false,
116                    // TODO: add Feature::LookbehindAssertion
117                    lookbehind_assertion: false,
118                    named_capturing_groups_regex: enable_named_capturing_groups_regex,
119                    sticky_regex: enable_sticky_regex,
120                    unicode_property_regex: enable_unicode_property_regex,
121                    unicode_regex: enable_unicode_regex,
122                    unicode_sets_regex: enable_unicode_sets_regex,
123                }),
124                enable,
125            ),
126        )
127    };
128
129    // Proposals
130
131    // ES2022
132    // static block needs to be placed before class property
133    // because it transforms into private static property
134
135    let pass = add!(pass, ClassStaticBlock, es2022::static_blocks());
136    let pass = add!(
137        pass,
138        ClassProperties,
139        es2022::class_properties(
140            es2022::class_properties::Config {
141                private_as_properties: loose || assumptions.private_fields_as_properties,
142                set_public_fields: loose || assumptions.set_public_class_fields,
143                constant_super: loose || assumptions.constant_super,
144                no_document_all: loose || assumptions.no_document_all,
145                pure_getter: loose || assumptions.pure_getters,
146            },
147            unresolved_mark
148        )
149    );
150    let pass = add!(pass, PrivatePropertyInObject, es2022::private_in_object());
151
152    // ES2021
153    let pass = add!(
154        pass,
155        LogicalAssignmentOperators,
156        es2021::logical_assignments()
157    );
158
159    // ES2020
160
161    let pass = add!(pass, ExportNamespaceFrom, es2020::export_namespace_from());
162    let pass = add!(
163        pass,
164        NullishCoalescing,
165        es2020::nullish_coalescing(es2020::nullish_coalescing::Config {
166            no_document_all: loose || assumptions.no_document_all
167        })
168    );
169
170    let pass = add!(
171        pass,
172        OptionalChaining,
173        es2020::optional_chaining(
174            es2020::optional_chaining::Config {
175                no_document_all: loose || assumptions.no_document_all,
176                pure_getter: loose || assumptions.pure_getters
177            },
178            unresolved_mark
179        )
180    );
181
182    // ES2019
183    let pass = add!(pass, OptionalCatchBinding, es2019::optional_catch_binding());
184
185    // ES2018
186    let pass = add!(
187        pass,
188        ObjectRestSpread,
189        es2018::object_rest_spread(es2018::object_rest_spread::Config {
190            no_symbol: loose || assumptions.object_rest_no_symbols,
191            set_property: loose || assumptions.set_spread_properties,
192            pure_getters: loose || assumptions.pure_getters
193        })
194    );
195
196    // ES2017
197    let pass = add!(
198        pass,
199        AsyncToGenerator,
200        es2017::async_to_generator(
201            es2017::async_to_generator::Config {
202                ignore_function_name: loose || assumptions.ignore_function_name,
203                ignore_function_length: loose || assumptions.ignore_function_length,
204            },
205            unresolved_mark
206        )
207    );
208
209    // ES2016
210    let pass = add!(pass, ExponentiationOperator, es2016::exponentiation());
211
212    // ES2015
213    let pass = add!(pass, BlockScopedFunctions, es2015::block_scoped_functions());
214    let pass = add!(
215        pass,
216        TemplateLiterals,
217        es2015::template_literal(es2015::template_literal::Config {
218            ignore_to_primitive: loose || assumptions.ignore_to_primitive_hint,
219            mutable_template: loose || assumptions.mutable_template_object
220        }),
221        true
222    );
223    let pass = add!(
224        pass,
225        Classes,
226        es2015::classes(es2015::classes::Config {
227            constant_super: loose || assumptions.constant_super,
228            no_class_calls: loose || assumptions.no_class_calls,
229            set_class_methods: loose || assumptions.set_class_methods,
230            super_is_callable_constructor: loose || assumptions.super_is_callable_constructor,
231        })
232    );
233    let pass = add!(
234        pass,
235        Spread,
236        es2015::spread(es2015::spread::Config { loose }),
237        true
238    );
239    let pass = add!(pass, ObjectSuper, es2015::object_super());
240    let pass = add!(pass, FunctionName, es2015::function_name());
241    let pass = add!(pass, ShorthandProperties, es2015::shorthand());
242    let pass = add!(
243        pass,
244        Parameters,
245        es2015::parameters(
246            es2015::parameters::Config {
247                ignore_function_length: loose || assumptions.ignore_function_length
248            },
249            unresolved_mark
250        )
251    );
252    let pass = add!(pass, ArrowFunctions, es2015::arrow(unresolved_mark));
253    let pass = add!(pass, DuplicateKeys, es2015::duplicate_keys());
254    let pass = add!(pass, StickyRegex, es2015::sticky_regex());
255    // TODO:    InstanceOf,
256    let pass = add!(pass, TypeOfSymbol, es2015::typeof_symbol());
257    let pass = add!(
258        pass,
259        ForOf,
260        es2015::for_of(es2015::for_of::Config {
261            loose,
262            assume_array: loose || assumptions.iterable_is_array
263        }),
264        true
265    );
266    let pass = add!(
267        pass,
268        ComputedProperties,
269        es2015::computed_properties(es2015::computed_props::Config { loose }),
270        true
271    );
272    let pass = add!(
273        pass,
274        Destructuring,
275        es2015::destructuring(es2015::destructuring::Config { loose }),
276        true
277    );
278    let pass = add!(
279        pass,
280        BlockScoping,
281        es2015::block_scoping(unresolved_mark),
282        true
283    );
284    let pass = add!(
285        pass,
286        Regenerator,
287        generator(unresolved_mark, comments),
288        true
289    );
290
291    let pass = add!(pass, NewTarget, es2015::new_target(), true);
292
293    // TODO:
294    //    Literals,
295    //    ObjectSuper,
296    //    DotAllRegex,
297    //    UnicodeRegex,
298    //    AsyncGeneratorFunctions,
299    //    UnicodePropertyRegex,
300    //    JsonStrings,
301    //    NamedCapturingGroupsRegex,
302
303    // ES 3
304    let pass = add!(pass, PropertyLiterals, es3::property_literals());
305    let pass = add!(
306        pass,
307        MemberExpressionLiterals,
308        es3::member_expression_literals()
309    );
310    let pass = add!(pass, ReservedWords, es3::reserved_words(c.dynamic_import));
311
312    // Bugfixes
313    let pass = add!(pass, BugfixEdgeDefaultParam, bugfixes::edge_default_param());
314    let pass = add!(
315        pass,
316        BugfixAsyncArrowsInClass,
317        bugfixes::async_arrows_in_class(unresolved_mark)
318    );
319    let pass = add!(
320        pass,
321        BugfixTaggedTemplateCaching,
322        bugfixes::template_literal_caching()
323    );
324    let pass = add!(
325        pass,
326        BugfixSafariIdDestructuringCollisionInFunctionExpression,
327        bugfixes::safari_id_destructuring_collision_in_function_expression()
328    );
329
330    if c.debug {
331        println!("Targets: {:?}", targets);
332    }
333
334    (
335        pass,
336        visit_mut_pass(Polyfills {
337            mode: c.mode,
338            regenerator: should_enable!(Regenerator, true),
339            corejs: c.core_js.unwrap_or(Version {
340                major: 3,
341                minor: 0,
342                patch: 0,
343            }),
344            shipped_proposals: c.shipped_proposals,
345            targets,
346            includes: included_modules,
347            excludes: excluded_modules,
348            unresolved_mark,
349        }),
350    )
351}
352
353#[derive(Debug)]
354struct Polyfills {
355    mode: Option<Mode>,
356    targets: Arc<Versions>,
357    shipped_proposals: bool,
358    corejs: Version,
359    regenerator: bool,
360    includes: FxHashSet<String>,
361    excludes: FxHashSet<String>,
362    unresolved_mark: Mark,
363}
364impl Polyfills {
365    fn collect<T>(&mut self, m: &mut T) -> Vec<Atom>
366    where
367        T: VisitWith<corejs2::UsageVisitor>
368            + VisitWith<corejs3::UsageVisitor>
369            + VisitMutWith<corejs2::Entry>
370            + VisitMutWith<corejs3::Entry>,
371    {
372        let required = match self.mode {
373            None => Default::default(),
374            Some(Mode::Usage) => {
375                let mut r = match self.corejs {
376                    Version { major: 2, .. } => {
377                        let mut v = corejs2::UsageVisitor::new(self.targets.clone());
378                        m.visit_with(&mut v);
379
380                        v.required
381                    }
382                    Version { major: 3, .. } => {
383                        let mut v = corejs3::UsageVisitor::new(
384                            self.targets.clone(),
385                            self.shipped_proposals,
386                            self.corejs,
387                        );
388                        m.visit_with(&mut v);
389                        v.required
390                    }
391
392                    _ => unimplemented!("corejs version other than 2 / 3"),
393                };
394
395                if regenerator::is_required(m) {
396                    r.insert("regenerator-runtime/runtime.js");
397                }
398
399                r
400            }
401            Some(Mode::Entry) => match self.corejs {
402                Version { major: 2, .. } => {
403                    let mut v = corejs2::Entry::new(self.targets.clone(), self.regenerator);
404                    m.visit_mut_with(&mut v);
405                    v.imports
406                }
407
408                Version { major: 3, .. } => {
409                    let mut v =
410                        corejs3::Entry::new(self.targets.clone(), self.corejs, !self.regenerator);
411                    m.visit_mut_with(&mut v);
412                    v.imports
413                }
414
415                _ => unimplemented!("corejs version other than 2 / 3"),
416            },
417        };
418        required
419            .iter()
420            .filter(|s| {
421                !s.starts_with("esnext") || !required.contains(&s.replace("esnext", "es").as_str())
422            })
423            .filter(|s| !self.excludes.contains(&***s))
424            .map(|s| -> Atom {
425                if *s != "regenerator-runtime/runtime.js" {
426                    format!("core-js/modules/{}.js", s).into()
427                } else {
428                    "regenerator-runtime/runtime.js".to_string().into()
429                }
430            })
431            .chain(self.includes.iter().map(|s| {
432                if s != "regenerator-runtime/runtime.js" {
433                    format!("core-js/modules/{}.js", s).into()
434                } else {
435                    "regenerator-runtime/runtime.js".to_string().into()
436                }
437            }))
438            .collect::<Vec<_>>()
439    }
440}
441impl VisitMut for Polyfills {
442    fn visit_mut_module(&mut self, m: &mut Module) {
443        let span = m.span;
444        let required = self.collect(m);
445        if cfg!(debug_assertions) {
446            let mut v = required.into_iter().collect::<Vec<_>>();
447            v.sort();
448            prepend_stmts(
449                &mut m.body,
450                v.into_iter().map(|src| {
451                    ImportDecl {
452                        span,
453                        specifiers: Vec::new(),
454                        src: Str {
455                            span: DUMMY_SP,
456                            raw: None,
457                            value: src,
458                        }
459                        .into(),
460                        type_only: false,
461                        with: None,
462                        phase: Default::default(),
463                    }
464                    .into()
465                }),
466            );
467        } else {
468            prepend_stmts(
469                &mut m.body,
470                required.into_iter().map(|src| {
471                    ImportDecl {
472                        span,
473                        specifiers: Vec::new(),
474                        src: Str {
475                            span: DUMMY_SP,
476                            raw: None,
477                            value: src,
478                        }
479                        .into(),
480                        type_only: false,
481                        with: None,
482                        phase: Default::default(),
483                    }
484                    .into()
485                }),
486            );
487        }
488
489        m.body.retain(|item| !matches!(item, ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl { src, .. })) if src.span == DUMMY_SP && src.value == atom!("")));
490    }
491
492    fn visit_mut_script(&mut self, m: &mut Script) {
493        let span = m.span;
494        let required = self.collect(m);
495        if cfg!(debug_assertions) {
496            let mut v = required.into_iter().collect::<Vec<_>>();
497            v.sort();
498            prepend_stmts(
499                &mut m.body,
500                v.into_iter().map(|src| {
501                    ExprStmt {
502                        span: DUMMY_SP,
503                        expr: CallExpr {
504                            span,
505                            callee: Ident {
506                                ctxt: SyntaxContext::empty().apply_mark(self.unresolved_mark),
507                                sym: "require".into(),
508                                ..Default::default()
509                            }
510                            .as_callee(),
511                            args: vec![Str {
512                                span: DUMMY_SP,
513                                value: src,
514                                raw: None,
515                            }
516                            .as_arg()],
517                            type_args: None,
518                            ..Default::default()
519                        }
520                        .into(),
521                    }
522                    .into()
523                }),
524            );
525        } else {
526            prepend_stmts(
527                &mut m.body,
528                required.into_iter().map(|src| {
529                    ExprStmt {
530                        span: DUMMY_SP,
531                        expr: CallExpr {
532                            span,
533                            callee: Ident {
534                                ctxt: SyntaxContext::empty().apply_mark(self.unresolved_mark),
535                                sym: "require".into(),
536                                ..Default::default()
537                            }
538                            .as_callee(),
539                            args: vec![Str {
540                                span: DUMMY_SP,
541                                value: src,
542                                raw: None,
543                            }
544                            .as_arg()],
545                            ..Default::default()
546                        }
547                        .into(),
548                    }
549                    .into()
550                }),
551            );
552        }
553    }
554}
555
556#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
557pub enum Mode {
558    #[serde(rename = "usage")]
559    Usage,
560    #[serde(rename = "entry")]
561    Entry,
562}
563
564#[derive(Debug, Clone, Deserialize, Default)]
565#[serde(rename_all = "camelCase")]
566pub struct Config {
567    #[serde(default)]
568    pub mode: Option<Mode>,
569
570    #[serde(default)]
571    pub debug: bool,
572
573    #[serde(default)]
574    pub dynamic_import: bool,
575
576    #[serde(default)]
577    pub loose: bool,
578
579    /// Skipped es features.
580    ///
581    /// e.g.)
582    ///  - `core-js/modules/foo`
583    #[serde(default)]
584    pub skip: Vec<Atom>,
585
586    #[serde(default)]
587    pub include: Vec<FeatureOrModule>,
588
589    #[serde(default)]
590    pub exclude: Vec<FeatureOrModule>,
591
592    /// The version of the used core js.
593    #[serde(default)]
594    pub core_js: Option<Version>,
595
596    #[serde(default)]
597    pub targets: Option<Targets>,
598
599    #[serde(default = "default_path")]
600    pub path: PathBuf,
601
602    #[serde(default)]
603    pub shipped_proposals: bool,
604
605    #[serde(default)]
606    pub force_all_transforms: bool,
607
608    #[serde(default)]
609    pub bugfixes: bool,
610}
611
612fn default_path() -> PathBuf {
613    if cfg!(target_arch = "wasm32") {
614        Default::default()
615    } else {
616        std::env::current_dir().unwrap()
617    }
618}
619
620#[derive(Debug, Clone, Deserialize, FromVariant)]
621#[serde(untagged)]
622pub enum FeatureOrModule {
623    Feature(Feature),
624    CoreJsModule(String),
625}
626
627impl FeatureOrModule {
628    pub fn split(vec: Vec<FeatureOrModule>) -> (Vec<Feature>, FxHashSet<String>) {
629        let mut features: Vec<_> = Default::default();
630        let mut modules: FxHashSet<_> = Default::default();
631
632        for v in vec {
633            match v {
634                FeatureOrModule::Feature(f) => features.push(f),
635                FeatureOrModule::CoreJsModule(m) => {
636                    modules.insert(m);
637                }
638            }
639        }
640
641        (features, modules)
642    }
643}