Repository: swc-project/swc (rust)

Issue:
Implement `assumptions` of babel
It can reduce the size of output.

https://babeljs.io/docs/en/assumptions

Pull request:
feat(es/transforms): Add `Assumptions`
swc_ecma_transforms_base:
 - Add `Assumptions`. (Closes #2057)

swc_ecma_transforms:
 - Reexport `Assumptions`.

swc:
 - Add `jsc.assumptions`.

---


~~cc @Austaras Can you check if I'm using correct assumptions instead of `loose`? (In `swc_ecma_transforms_compat`)~~

We can patch passes to use `assumptions` instead of `loose` gradually.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 06e875d11c..8c0e790de3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3031,6 +3031,7 @@ dependencies = [
  "phf",
  "rayon",
  "scoped-tls",
+ "serde",
  "smallvec",
  "swc_atoms",
  "swc_common",
diff --git a/crates/swc/src/builder.rs b/crates/swc/src/builder.rs
index fd6b8a36b6..467e6ce023 100644
--- a/crates/swc/src/builder.rs
+++ b/crates/swc/src/builder.rs
@@ -16,7 +16,7 @@ use swc_ecma_parser::Syntax;
 use swc_ecma_transforms::{
     compat, compat::es2022::private_in_object, fixer, helpers, hygiene,
     hygiene::hygiene_with_config, modules, modules::util::Scope, optimization::const_modules,
-    pass::Optional,
+    pass::Optional, Assumptions,
 };
 use swc_ecma_visit::{as_folder, noop_visit_mut_type, VisitMut};
 
@@ -30,6 +30,7 @@ pub struct PassBuilder<'a, 'b, P: swc_ecma_visit::Fold> {
     top_level_mark: Mark,
     target: EsVersion,
     loose: bool,
+    assumptions: Assumptions,
     hygiene: Option<hygiene::Config>,
     fixer: bool,
     inject_helpers: bool,
@@ -42,6 +43,7 @@ impl<'a, 'b, P: swc_ecma_visit::Fold> PassBuilder<'a, 'b, P> {
         cm: &'a Arc<SourceMap>,
         handler: &'b Handler,
         loose: bool,
+        assumptions: Assumptions,
         top_level_mark: Mark,
         pass: P,
     ) -> Self {
@@ -53,6 +55,7 @@ impl<'a, 'b, P: swc_ecma_visit::Fold> PassBuilder<'a, 'b, P> {
             top_level_mark,
             target: EsVersion::Es5,
             loose,
+            assumptions,
             hygiene: Some(Default::default()),
             fixer: true,
             inject_helpers: true,
@@ -74,6 +77,7 @@ impl<'a, 'b, P: swc_ecma_visit::Fold> PassBuilder<'a, 'b, P> {
             top_level_mark: self.top_level_mark,
             target: self.target,
             loose: self.loose,
+            assumptions: self.assumptions,
             hygiene: self.hygiene,
             fixer: self.fixer,
             inject_helpers: self.inject_helpers,
diff --git a/crates/swc/src/config/mod.rs b/crates/swc/src/config/mod.rs
index 486dd2bdc7..9d9580a0d7 100644
--- a/crates/swc/src/config/mod.rs
+++ b/crates/swc/src/config/mod.rs
@@ -48,7 +48,7 @@ use swc_ecma_transforms::{
     optimization::{const_modules, json_parse, simplifier},
     pass::{noop, Optional},
     proposals::{decorators, export_default_from, import_assertions},
-    react, resolver_with_mark, typescript,
+    react, resolver_with_mark, typescript, Assumptions,
 };
 use swc_ecma_transforms_compat::es2015::regenerator;
 use swc_ecma_transforms_optimization::{inline_globals2, GlobalExprMap};
@@ -266,6 +266,7 @@ impl Options {
         source_maps.merge(&config.source_maps);
 
         let JscConfig {
+            assumptions,
             transform,
             syntax,
             external_helpers,
@@ -279,6 +280,14 @@ impl Options {
             ..
         } = config.jsc;
 
+        let assumptions = assumptions.unwrap_or_else(|| {
+            if loose {
+                Assumptions::all()
+            } else {
+                Assumptions::default()
+            }
+        });
+
         let target = target.unwrap_or_default();
 
         let syntax = syntax.unwrap_or_default();
@@ -369,7 +378,7 @@ impl Options {
             json_parse_pass
         );
 
-        let pass = PassBuilder::new(&cm, &handler, loose, top_level_mark, pass)
+        let pass = PassBuilder::new(&cm, &handler, loose, assumptions, top_level_mark, pass)
             .target(target)
             .skip_helper_injection(self.skip_helper_injection)
             .minify(js_minify)
@@ -930,6 +939,9 @@ pub struct BuiltInput<P: swc_ecma_visit::Fold> {
 #[derive(Debug, Default, Clone, Serialize, Deserialize)]
 #[serde(deny_unknown_fields, rename_all = "camelCase")]
 pub struct JscConfig {
+    #[serde(default)]
+    pub assumptions: Option<Assumptions>,
+
     #[serde(rename = "parser", default)]
     pub syntax: Option<Syntax>,
 
diff --git a/crates/swc_ecma_transforms/src/lib.rs b/crates/swc_ecma_transforms/src/lib.rs
index 97d9456b89..681d3dd38f 100644
--- a/crates/swc_ecma_transforms/src/lib.rs
+++ b/crates/swc_ecma_transforms/src/lib.rs
@@ -6,7 +6,9 @@ pub use self::{
     hygiene::hygiene,
     resolver::{resolver, resolver_with_mark},
 };
-pub use swc_ecma_transforms_base::{fixer, helpers, hygiene, pass, perf, resolver};
+pub use swc_ecma_transforms_base::{
+    assumptions::Assumptions, fixer, helpers, hygiene, pass, perf, resolver,
+};
 #[cfg(feature = "swc_ecma_transforms_compat")]
 #[cfg_attr(docsrs, doc(cfg(feature = "compat")))]
 pub use swc_ecma_transforms_compat as compat;
diff --git a/crates/swc_ecma_transforms_base/Cargo.toml b/crates/swc_ecma_transforms_base/Cargo.toml
index d86707b47d..38932931dd 100644
--- a/crates/swc_ecma_transforms_base/Cargo.toml
+++ b/crates/swc_ecma_transforms_base/Cargo.toml
@@ -19,6 +19,7 @@ once_cell = "1.5.2"
 phf = {version = "0.8.0", features = ["macros"]}
 rayon = {version = "1", optional = true}
 scoped-tls = "1.0.0"
+serde = {version = "1", features = ["derive"]}
 smallvec = "1.6.0"
 swc_atoms = {version = "0.2", path = "../swc_atoms"}
 swc_common = {version = "0.16.0", path = "../swc_common"}
diff --git a/crates/swc_ecma_transforms_base/src/assumptions.rs b/crates/swc_ecma_transforms_base/src/assumptions.rs
new file mode 100644
index 0000000000..644cbf2696
--- /dev/null
+++ b/crates/swc_ecma_transforms_base/src/assumptions.rs
@@ -0,0 +1,101 @@
+use serde::{Deserialize, Serialize};
+
+/// Alternative for https://babeljs.io/docs/en/assumptions
+///
+/// All fields default to `false`.
+#[derive(
+    Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
+)]
+#[non_exhaustive]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+pub struct Assumptions {
+    /// https://babeljs.io/docs/en/assumptions#arraylikeisiterable
+    pub array_like_is_iterable: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#constantreexports
+    pub constant_reexports: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#constantsuper
+    pub constant_super: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#enumerablemodulemeta
+    pub enumerable_module_meta: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#ignorefunctionlength
+    pub ignore_function_length: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#ignoretoprimitivehint
+    pub ignore_to_primitive_hint: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#iterableisarray
+    pub iterable_is_array: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#mutabletemplateobject
+    pub mutable_template_object: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#noclasscalls
+    pub no_class_calls: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#nodocumentall
+    pub no_document_all: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#noincompletensimportdetection
+    pub no_incomplete_ns_import_detection: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#nonewarrows
+    pub no_new_arrows: bool,
+    /// https://babeljs.io/docs/en/assumptions#objectrestnosymbols
+    pub object_rest_no_symbols: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#privatefieldsasproperties
+    pub private_fields_as_properties: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#puregetters
+    pub pure_getters: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#setclassmethods
+    pub set_class_methods: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#setcomputedproperties
+    pub set_computed_properties: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#setpublicclassfields
+    pub set_public_class_fields: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#setspreadproperties
+    pub set_spread_properties: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#skipforofiteratorclosing
+    pub skip_for_of_iterator_closing: bool,
+
+    /// https://babeljs.io/docs/en/assumptions#superiscallableconstructor
+    pub super_is_callable_constructor: bool,
+}
+
+impl Assumptions {
+    pub fn all() -> Self {
+        Self {
+            array_like_is_iterable: true,
+            constant_reexports: true,
+            constant_super: true,
+            enumerable_module_meta: true,
+            ignore_function_length: true,
+            ignore_to_primitive_hint: true,
+            iterable_is_array: true,
+            mutable_template_object: true,
+            no_class_calls: true,
+            no_document_all: true,
+            no_incomplete_ns_import_detection: true,
+            no_new_arrows: true,
+            object_rest_no_symbols: true,
+            private_fields_as_properties: true,
+            pure_getters: true,
+            set_class_methods: true,
+            set_computed_properties: true,
+            set_public_class_fields: true,
+            set_spread_properties: true,
+            skip_for_of_iterator_closing: true,
+            super_is_callable_constructor: true,
+        }
+    }
+}
diff --git a/crates/swc_ecma_transforms_base/src/lib.rs b/crates/swc_ecma_transforms_base/src/lib.rs
index 750c331b0d..de30975392 100644
--- a/crates/swc_ecma_transforms_base/src/lib.rs
+++ b/crates/swc_ecma_transforms_base/src/lib.rs
@@ -3,6 +3,7 @@ pub mod ext;
 pub mod fixer;
 #[macro_use]
 pub mod hygiene;
+pub mod assumptions;
 pub mod helpers;
 #[doc(hidden)]
 pub mod native;


Test patch:
diff --git a/crates/swc/tests/rust_api.rs b/crates/swc/tests/rust_api.rs
index 36fe22e7d1..511b462b75 100644
--- a/crates/swc/tests/rust_api.rs
+++ b/crates/swc/tests/rust_api.rs
@@ -144,6 +144,7 @@ fn shopify_2_same_opt() {
                     paths: Default::default(),
                     minify: None,
                     experimental: Default::default(),
+                    assumptions: Default::default(),
                 },
                 module: None,
                 minify: false,