Repository: swc-project/swc (rust)

Issue:
Is swc support options like Babel-loader "sourceType":  "unambiguous" ?
<!--
If you are using swc at work, please considering adding your company to https://swc.rs/users/
If then, your issue will be fixed more quickly.
-->

**Describe the feature**
Is there an option like babel-loader `sourceType: "script" | "module" | "unambiguous"`

**Babel plugin or link to the feature description**
https://babeljs.io/docs/en/options#sourcetype

**Additional context**



Pull request:
feat: Introduce is_module unknown
This PR introduces the `isModule: 'unknown'` flag to auto-detect whether a program is a module or script.

Open points:

- [x] Add tests
- [x] Figure out why test for issue 706 is failing (currently commented out)
- [x] Figure out why a bunch of other tests are failing too

Resolves #2541 

Patch:
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 01a9cbd5aa..29ea0c2e6a 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -46,7 +46,7 @@ Parses javascript and typescript
 
 ### [`/crates/swc_ecma_transforms_base`](crates/swc_ecma_transforms_base)
 
-Theres are three core transforms named `resolver`, `hygiene`, `fixer`. Other transforms depends on them.
+There are three core transforms named `resolver`, `hygiene`, `fixer`. Other transforms depend on them.
 
 #### [`/crates/swc_ecma_transforms_base/src/resolver`](crates/swc_ecma_transforms_base/src/resolver)
 
@@ -70,11 +70,11 @@ let a#0 = 1;
 }
 ```
 
-where number after `#` denotes the hygiene id. If two identifiers have same symbol but different hygiene id, it's different.
+where the number after the hash (`#`) denotes the hygiene id. If two identifiers have the same symbol but different hygiene ids, they are considered different.
 
 #### [`/crates/swc_ecma_transforms_base/src/hygiene`](crates/swc_ecma_transforms_base/src/hygiene)
 
-Hygiene pass actually changes symbol of identifiers with same symbol but different hygiene id.
+The hygiene pass actually changes symbols of identifiers with the same symbol but different hygiene ids to different symbols.
 
 ```js
 let a#0 = 1;
@@ -106,7 +106,7 @@ let v = BinExpr {
 };
 ```
 
-(other passes generates AST like this)
+(other passes generate AST like this)
 
 is converted into
 
@@ -127,7 +127,7 @@ and printed as
 <!-- TODO: add correct references to files -->
 <!-- #### `/ecmascript/transforms/src/compat`
 
-Contains code related to converting new generation javascript codes for old browsers.
+Contains code related to converting new generation javascript code into code understood by old browsers.
 
 #### `/ecmascript/transforms/src/modules`
 
@@ -135,15 +135,15 @@ Contains code related to transforming es6 modules to other modules.
 
 #### `/ecmascript/transforms/src/optimization`
 
-Contains code related to making code faster on runtime. Currently only small set of optimization is implemented. -->
+Contains code related to making code faster on runtime. Currently only a small set of optimizations is implemented. -->
 
 ## Tests
 
 SWC uses the [official ecmascript conformance test suite called test262][test262] for testing.
 
-Parser tests ensures that parsed result of `test262/pass` is identical with `test262/pass-explicit`.
+Parser tests ensure that the parsed results of `test262/pass` are identical with `test262/pass-explicit`.
 
-Codegen tests ensures that generated code is equivalent to golden fixture files located at [tests/references](crates/swc_ecma_codegen/tests).
+Codegen tests ensure that the generated code is equivalent to the golden fixture files located at [tests/references](crates/swc_ecma_codegen/tests).
 
 [enum_kind]: https://rustdoc.swc.rs/enum_kind/derive.Kind.html
 [string_enum]: https://rustdoc.swc.rs/string_enum/derive.StringEnum.html
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 04c21ee61d..9eaa78ed6e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -76,8 +76,8 @@ After cloning the project there are a few steps required to get the project runn
 2.  Install js dependencies.
 
     ```bash
-    yarn add browserslist
-    ( cd ecmascript/transforms; yarn install )
+    yarn
+    ( cd ecmascript/transforms; yarn )
     ```
 
 3.  Setup some environment variables which is required for tests.
@@ -85,13 +85,18 @@ After cloning the project there are a few steps required to get the project runn
     ```bash
     export RUST_BACKTRACE=full
     export PATH="$PATH:$PWD/ecmascript/transforms/node_modules/.bin"
+    export RUST_MIN_STACK=16777216
     ```
 
 4.  Install deno, if you are going to work on the bundler.
 
     See [official install guide of deno](https://deno.land/manual/getting_started/installation) to install it.
 
-5.  Run tests
+5.  Ensure you're using Node.JS >= 16
+
+    Since tests make use of `atob` which was only introduced in node 16.
+
+6.  Run tests
 
     ```bash
     cargo test --all --all-features
diff --git a/crates/swc/benches/bugs.rs b/crates/swc/benches/bugs.rs
index 2549955773..d268de4831 100644
--- a/crates/swc/benches/bugs.rs
+++ b/crates/swc/benches/bugs.rs
@@ -5,7 +5,7 @@ extern crate swc_node_base;
 extern crate test;
 
 use std::{fs::read_to_string, io::stderr, path::Path};
-use swc::config::Options;
+use swc::config::{IsModule, Options};
 use swc_common::{errors::Handler, sync::Lrc, FilePathMapping, SourceMap};
 use test::Bencher;
 
@@ -32,7 +32,7 @@ fn bench_file(b: &mut Bencher, path: &Path) {
                 fm.clone(),
                 &handler,
                 &Options {
-                    is_module: true,
+                    is_module: IsModule::Bool(true),
                     ..Default::default()
                 },
             )
diff --git a/crates/swc/benches/typescript.rs b/crates/swc/benches/typescript.rs
index c3eee88261..0181cc6fd2 100644
--- a/crates/swc/benches/typescript.rs
+++ b/crates/swc/benches/typescript.rs
@@ -9,7 +9,7 @@ use std::{
     io::{self, stderr},
     sync::Arc,
 };
-use swc::config::{Config, JscConfig, Options, SourceMapsConfig};
+use swc::config::{Config, IsModule, JscConfig, Options, SourceMapsConfig};
 use swc_common::{errors::Handler, FileName, FilePathMapping, SourceFile, SourceMap};
 use swc_ecma_ast::{EsVersion, Program};
 use swc_ecma_parser::{Syntax, TsConfig};
@@ -41,7 +41,7 @@ fn parse(c: &swc::Compiler) -> (Arc<SourceFile>, Program) {
             &handler,
             EsVersion::Es5,
             Syntax::Typescript(Default::default()),
-            true,
+            IsModule::Bool(true),
             true,
         )
         .unwrap(),
@@ -165,7 +165,7 @@ macro_rules! compat {
                         ..Default::default()
                     },
                     swcrc: false,
-                    is_module: true,
+                    is_module: IsModule::Bool(true),
                     ..Default::default()
                 },
             );
diff --git a/crates/swc/src/config/mod.rs b/crates/swc/src/config/mod.rs
index 36b231a993..9c617f68a0 100644
--- a/crates/swc/src/config/mod.rs
+++ b/crates/swc/src/config/mod.rs
@@ -6,11 +6,14 @@ use either::Either;
 use indexmap::IndexMap;
 use once_cell::sync::Lazy;
 use regex::Regex;
-use serde::{Deserialize, Serialize};
+use serde::{
+    de::{Unexpected, Visitor},
+    Deserialize, Deserializer, Serialize, Serializer,
+};
 use std::{
     cell::RefCell,
     collections::{HashMap, HashSet},
-    env,
+    env, fmt,
     hash::BuildHasher,
     path::{Path, PathBuf},
     rc::Rc as RustRc,
@@ -54,6 +57,66 @@ use swc_ecma_visit::Fold;
 mod tests;
 pub mod util;
 
+#[derive(Clone, Debug, Copy)]
+pub enum IsModule {
+    Bool(bool),
+    Unknown,
+}
+
+impl Default for IsModule {
+    fn default() -> Self {
+        IsModule::Bool(true)
+    }
+}
+
+impl Serialize for IsModule {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        match *self {
+            IsModule::Bool(ref b) => b.serialize(serializer),
+            IsModule::Unknown => "unknown".serialize(serializer),
+        }
+    }
+}
+
+impl<'de> Deserialize<'de> for IsModule {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct IsModuleVisitor;
+
+        impl<'de> Visitor<'de> for IsModuleVisitor {
+            type Value = IsModule;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                formatter.write_str("a boolean or the string 'unknown'")
+            }
+
+            fn visit_bool<E>(self, b: bool) -> Result<Self::Value, E>
+            where
+                E: serde::de::Error,
+            {
+                return Ok(IsModule::Bool(b));
+            }
+
+            fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
+            where
+                E: serde::de::Error,
+            {
+                match s {
+                    "unknown" => Ok(IsModule::Unknown),
+                    _ => Err(serde::de::Error::invalid_value(Unexpected::Str(s), &self)),
+                }
+            }
+        }
+
+        deserializer.deserialize_any(IsModuleVisitor)
+    }
+}
+
 #[derive(Default, Clone, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub struct ParseOptions {
@@ -62,8 +125,8 @@ pub struct ParseOptions {
     #[serde(flatten)]
     pub syntax: Syntax,
 
-    #[serde(default = "default_is_module")]
-    pub is_module: bool,
+    #[serde(default)]
+    pub is_module: IsModule,
 
     #[serde(default)]
     pub target: EsVersion,
@@ -125,8 +188,8 @@ pub struct Options {
     #[serde(default)]
     pub source_root: Option<String>,
 
-    #[serde(default = "default_is_module")]
-    pub is_module: bool,
+    #[serde(default)]
+    pub is_module: IsModule,
 
     #[serde(default)]
     pub output_path: Option<PathBuf>,
@@ -138,10 +201,6 @@ impl Options {
     }
 }
 
-fn default_is_module() -> bool {
-    true
-}
-
 /// Configuration related to source map generated by swc.
 #[derive(Clone, Serialize, Deserialize, Debug)]
 #[serde(untagged)]
@@ -187,11 +246,11 @@ impl Options {
         &self,
         cm: &Arc<SourceMap>,
         base: &FileName,
-        parse: impl FnOnce(Syntax, EsVersion, bool) -> Result<Program, Error>,
+        parse: impl FnOnce(Syntax, EsVersion, IsModule) -> Result<Program, Error>,
         output_path: Option<&Path>,
         source_file_name: Option<String>,
         handler: &Handler,
-        is_module: bool,
+        is_module: IsModule,
         config: Option<Config>,
         comments: Option<&'a SwcComments>,
         custom_before_pass: impl FnOnce(&Program) -> P,
@@ -813,7 +872,7 @@ pub struct BuiltInput<P: swc_ecma_visit::Fold> {
     pub external_helpers: bool,
     pub source_maps: SourceMapsConfig,
     pub input_source_map: InputSourceMap,
-    pub is_module: bool,
+    pub is_module: IsModule,
     pub output_path: Option<PathBuf>,
 
     pub source_file_name: Option<String>,
diff --git a/crates/swc/src/lib.rs b/crates/swc/src/lib.rs
index 84ef4ec6a6..b64e7138a0 100644
--- a/crates/swc/src/lib.rs
+++ b/crates/swc/src/lib.rs
@@ -120,7 +120,7 @@ use common::{
     collections::AHashMap,
     errors::{EmitterWriter, HANDLER},
 };
-use config::{util::BoolOrObject, JsMinifyCommentOption, JsMinifyOptions};
+use config::{util::BoolOrObject, IsModule, JsMinifyCommentOption, JsMinifyOptions};
 use dashmap::DashMap;
 use once_cell::sync::Lazy;
 use serde::Serialize;
@@ -399,7 +399,7 @@ impl Compiler {
         handler: &Handler,
         target: EsVersion,
         syntax: Syntax,
-        is_module: bool,
+        is_module: IsModule,
         parse_comments: bool,
     ) -> Result<Program, Error> {
         self.run(|| {
@@ -415,33 +415,22 @@ impl Compiler {
             );
             let mut parser = Parser::new_from(lexer);
             let mut error = false;
-            let program = if is_module {
-                let m = parser.parse_module();
 
-                for e in parser.take_errors() {
-                    e.into_diagnostic(handler).emit();
-                    error = true;
-                }
-
-                m.map_err(|e| {
-                    e.into_diagnostic(handler).emit();
-                    Error::msg("Syntax Error")
-                })
-                .map(Program::Module)?
-            } else {
-                let s = parser.parse_script();
+            let program_result = match is_module {
+                IsModule::Bool(true) => parser.parse_module().map(Program::Module),
+                IsModule::Bool(false) => parser.parse_script().map(Program::Script),
+                IsModule::Unknown => parser.parse_program(),
+            };
 
-                for e in parser.take_errors() {
-                    e.into_diagnostic(handler).emit();
-                    error = true;
-                }
+            for e in parser.take_errors() {
+                e.into_diagnostic(handler).emit();
+                error = true;
+            }
 
-                s.map_err(|e| {
-                    e.into_diagnostic(handler).emit();
-                    Error::msg("Syntax Error")
-                })
-                .map(Program::Script)?
-            };
+            let program = program_result.map_err(|e| {
+                e.into_diagnostic(handler).emit();
+                Error::msg("Syntax Error")
+            })?;
 
             if error {
                 return Err(anyhow::anyhow!("Syntax Error").context(
@@ -982,7 +971,7 @@ impl Compiler {
 
                         ..Default::default()
                     }),
-                    true,
+                    IsModule::Bool(true),
                     true,
                 )
                 .context("failed to parse input file")?
diff --git a/crates/swc_babel_compat/benches/babelify.rs b/crates/swc_babel_compat/benches/babelify.rs
index 6c43f41995..8a1384dd28 100644
--- a/crates/swc_babel_compat/benches/babelify.rs
+++ b/crates/swc_babel_compat/benches/babelify.rs
@@ -4,6 +4,7 @@
 extern crate test;
 
 use std::{hint::black_box, io::stderr, sync::Arc};
+use swc::config::IsModule;
 use swc_babel_compat::babelify::{Babelify, Context};
 use swc_common::{errors::Handler, FileName, FilePathMapping, SourceFile, SourceMap};
 use swc_ecma_ast::{EsVersion, Program};
@@ -36,7 +37,7 @@ fn parse(c: &swc::Compiler, src: &str) -> (Arc<SourceFile>, Program) {
             &handler,
             EsVersion::Es5,
             Syntax::Typescript(Default::default()),
-            true,
+            IsModule::Bool(true),
             true,
         )
         .unwrap();
diff --git a/crates/swc_node_bundler/src/loaders/swc.rs b/crates/swc_node_bundler/src/loaders/swc.rs
index becc07c521..c596b240a2 100644
--- a/crates/swc_node_bundler/src/loaders/swc.rs
+++ b/crates/swc_node_bundler/src/loaders/swc.rs
@@ -3,7 +3,7 @@ use anyhow::{bail, Context, Error};
 use helpers::Helpers;
 use std::{collections::HashMap, env, sync::Arc};
 use swc::{
-    config::{GlobalInliningPassEnvs, InputSourceMap, JscConfig, TransformConfig},
+    config::{GlobalInliningPassEnvs, InputSourceMap, IsModule, JscConfig, TransformConfig},
     try_with_handler,
 };
 use swc_atoms::JsWord;
@@ -159,7 +159,7 @@ impl SwcLoader {
                 &handler,
                 EsVersion::Es2020,
                 Default::default(),
-                true,
+                IsModule::Bool(true),
                 true,
             )?;
             let program = helpers::HELPERS.set(&helpers, || {
@@ -230,7 +230,7 @@ impl SwcLoader {
                     source_maps: None,
                     source_file_name: None,
                     source_root: None,
-                    is_module: true,
+                    is_module: IsModule::Bool(true),
                     output_path: None,
                     ..Default::default()
                 },
@@ -269,7 +269,7 @@ impl SwcLoader {
                         handler,
                         EsVersion::Es2020,
                         config.as_ref().map(|v| v.syntax).unwrap_or_default(),
-                        true,
+                        IsModule::Bool(true),
                         true,
                     )
                     .context("tried to parse as ecmascript as it's excluded by .swcrc")?
diff --git a/node-swc/src/types.ts b/node-swc/src/types.ts
index 8ff0383dd6..3cc7314bfc 100644
--- a/node-swc/src/types.ts
+++ b/node-swc/src/types.ts
@@ -380,7 +380,7 @@ export interface Options extends Config {
 
   plugin?: Plugin;
 
-  isModule?: boolean;
+  isModule?: boolean | 'unknown';
 
   /**
    * Destination path. Note that this value is used only to fix source path


Test patch:
diff --git a/crates/swc/tests/error_msg.rs b/crates/swc/tests/error_msg.rs
index 459d119f6b..f0e8e0e3d9 100644
--- a/crates/swc/tests/error_msg.rs
+++ b/crates/swc/tests/error_msg.rs
@@ -1,5 +1,8 @@
 use std::path::Path;
-use swc::{config::Options, Compiler};
+use swc::{
+    config::{IsModule, Options},
+    Compiler,
+};
 use testing::{NormalizedOutput, Tester};
 
 fn file(f: impl AsRef<Path>) -> NormalizedOutput {
@@ -13,7 +16,7 @@ fn file(f: impl AsRef<Path>) -> NormalizedOutput {
                 &handler,
                 &Options {
                     swcrc: true,
-                    is_module: true,
+                    is_module: IsModule::Bool(true),
                     ..Default::default()
                 },
             );
diff --git a/crates/swc/tests/projects.rs b/crates/swc/tests/projects.rs
index 1d8e3d2ab5..608b6b7de7 100644
--- a/crates/swc/tests/projects.rs
+++ b/crates/swc/tests/projects.rs
@@ -7,7 +7,8 @@ use std::{
 };
 use swc::{
     config::{
-        BuiltInput, Config, JscConfig, ModuleConfig, Options, SourceMapsConfig, TransformConfig,
+        BuiltInput, Config, IsModule, JscConfig, ModuleConfig, Options, SourceMapsConfig,
+        TransformConfig,
     },
     Compiler, TransformOutput,
 };
@@ -44,7 +45,7 @@ fn file_with_opt(filename: &str, options: Options) -> Result<NormalizedOutput, S
             fm,
             &handler,
             &Options {
-                is_module: true,
+                is_module: IsModule::Bool(true),
                 ..options
             },
         );
@@ -79,7 +80,7 @@ fn compile_str(
             fm,
             &handler,
             &Options {
-                is_module: true,
+                is_module: IsModule::Bool(true),
                 ..options
             },
         );
@@ -121,7 +122,7 @@ fn project(dir: &str) {
                 if c.read_config(
                     &Options {
                         swcrc: true,
-                        is_module: true,
+                        is_module: IsModule::Bool(true),
 
                         ..Default::default()
                     },
@@ -138,7 +139,7 @@ fn project(dir: &str) {
                     &handler,
                     &Options {
                         swcrc: true,
-                        is_module: true,
+                        is_module: IsModule::Bool(true),
 
                         ..Default::default()
                     },
@@ -189,7 +190,7 @@ fn par_project(dir: &str) {
                     &handler,
                     &Options {
                         swcrc: true,
-                        is_module: true,
+                        is_module: IsModule::Bool(true),
                         source_maps: Some(SourceMapsConfig::Bool(true)),
                         ..Default::default()
                     },
@@ -540,7 +541,7 @@ fn issue_879() {
     let f = file_with_opt(
         "tests/projects/issue-879/input.ts",
         Options {
-            is_module: true,
+            is_module: IsModule::Bool(true),
             config: Config {
                 env: Some(Default::default()),
                 module: Some(ModuleConfig::CommonJs(Default::default())),
@@ -614,7 +615,7 @@ fn issue_1549() {
     let output = str_with_opt(
         "const a = `\r\n`;",
         Options {
-            is_module: true,
+            is_module: IsModule::Bool(true),
             config: Config {
                 jsc: JscConfig {
                     target: Some(EsVersion::Es5),
@@ -636,7 +637,7 @@ fn deno_10282_1() {
     let output = str_with_opt(
         "const a = `\r\n`;",
         Options {
-            is_module: true,
+            is_module: IsModule::Bool(true),
             config: Config {
                 jsc: JscConfig {
                     target: Some(EsVersion::Es3),
@@ -658,7 +659,7 @@ fn deno_10282_2() {
     let output = str_with_opt(
         "const a = `\r\n`;",
         Options {
-            is_module: true,
+            is_module: IsModule::Bool(true),
             config: Config {
                 jsc: JscConfig {
                     target: Some(EsVersion::Es2020),
@@ -711,7 +712,7 @@ fn should_visit() {
                     None,
                     &handler,
                     &swc::config::Options {
-                        is_module: true,
+                        is_module: IsModule::Bool(true),
                         config: swc::config::Config {
                             jsc: JscConfig {
                                 syntax: Some(Syntax::Es(EsConfig {
@@ -818,7 +819,7 @@ fn tests(input_dir: PathBuf) {
                     &handler,
                     &Options {
                         swcrc: true,
-                        is_module: true,
+                        is_module: IsModule::Bool(true),
                         output_path: Some(output.join(entry.file_name())),
 
                         ..Default::default()
diff --git a/crates/swc/tests/rust_api.rs b/crates/swc/tests/rust_api.rs
index c8928015da..8e928bf486 100644
--- a/crates/swc/tests/rust_api.rs
+++ b/crates/swc/tests/rust_api.rs
@@ -1,5 +1,7 @@
 use swc::{
-    config::{Config, InputSourceMap, JscConfig, ModuleConfig, Options, SourceMapsConfig},
+    config::{
+        Config, InputSourceMap, IsModule, JscConfig, ModuleConfig, Options, SourceMapsConfig,
+    },
     Compiler,
 };
 use swc_common::FileName;
@@ -96,7 +98,7 @@ fn shopify_1_check_filename() {
                     })),
                     ..Default::default()
                 },
-                is_module: true,
+                is_module: IsModule::Bool(true),
                 ..Default::default()
             },
             |_| noop(),
@@ -161,7 +163,7 @@ fn shopify_2_same_opt() {
             env_name: "development".into(),
             source_maps: Some(SourceMapsConfig::Bool(false)),
             source_file_name: Some("/Users/kdy1/projects/example-swcify/src/App/App.tsx".into()),
-            is_module: true,
+            is_module: IsModule::Bool(true),
             ..Default::default()
         };
 
@@ -223,7 +225,7 @@ fn shopify_3_reduce_defaults() {
             env_name: "development".into(),
             source_maps: Some(SourceMapsConfig::Bool(false)),
             source_file_name: Some("/Users/kdy1/projects/example-swcify/src/App/App.tsx".into()),
-            is_module: true,
+            is_module: IsModule::Bool(true),
             ..Default::default()
         };
 
@@ -279,7 +281,7 @@ fn shopify_4_reduce_more() {
             env_name: "development".into(),
             source_maps: Some(SourceMapsConfig::Bool(false)),
             source_file_name: Some("/Users/kdy1/projects/example-swcify/src/App/App.tsx".into()),
-            is_module: true,
+            is_module: IsModule::Bool(true),
             ..Default::default()
         };
 
diff --git a/crates/swc/tests/simple.rs b/crates/swc/tests/simple.rs
index 34658b71ee..66331c342a 100644
--- a/crates/swc/tests/simple.rs
+++ b/crates/swc/tests/simple.rs
@@ -1,5 +1,5 @@
 use swc::{
-    config::{Config, JscConfig, Options},
+    config::{Config, IsModule, JscConfig, Options},
     Compiler,
 };
 use swc_common::FileName;
@@ -17,7 +17,7 @@ fn compile(src: &str, options: Options) -> String {
                 fm,
                 &handler,
                 &Options {
-                    is_module: true,
+                    is_module: IsModule::Bool(true),
                     ..options
                 },
             );
@@ -147,3 +147,37 @@ fn test_tsx_escape_xhtml() {
 
     assert_eq!(compiled_es2020, expected);
 }
+
+#[test]
+fn is_module_unknown_script() {
+    let source = "module.exports = foo =  2n + 7n;";
+    let expected = "module.exports = foo = 2n + 7n;\n";
+
+    let compiled = compile(
+        source,
+        Options {
+            swcrc: false,
+            is_module: IsModule::Unknown,
+            ..Default::default()
+        },
+    );
+
+    assert_eq!(compiled, expected);
+}
+
+#[test]
+fn is_module_unknown_module() {
+    let source = "export var foo =  2n + 7n;";
+    let expected = "export var foo = 2n + 7n;\n";
+
+    let compiled = compile(
+        source,
+        Options {
+            swcrc: false,
+            is_module: IsModule::Unknown,
+            ..Default::default()
+        },
+    );
+
+    assert_eq!(compiled, expected);
+}
diff --git a/crates/swc/tests/source_map.rs b/crates/swc/tests/source_map.rs
index 7d4122b10a..53eb9ebc6b 100644
--- a/crates/swc/tests/source_map.rs
+++ b/crates/swc/tests/source_map.rs
@@ -10,7 +10,7 @@ use std::{
     sync::Arc,
 };
 use swc::{
-    config::{Config, Options, SourceMapsConfig},
+    config::{Config, IsModule, Options, SourceMapsConfig},
     Compiler,
 };
 use testing::{assert_eq, NormalizedOutput, StdErr, Tester};
@@ -33,7 +33,7 @@ fn file(f: &str) -> Result<(), StdErr> {
                         ..Default::default()
                     },
                     swcrc: true,
-                    is_module: true,
+                    is_module: IsModule::Bool(true),
                     source_maps: Some(SourceMapsConfig::Bool(true)),
                     ..Default::default()
                 },
@@ -88,7 +88,7 @@ fn inline(f: &str) -> Result<(), StdErr> {
                         ..Default::default()
                     },
                     swcrc: true,
-                    is_module: true,
+                    is_module: IsModule::Bool(true),
                     source_maps: Some(SourceMapsConfig::Str(String::from("inline"))),
                     ..Default::default()
                 },
@@ -153,7 +153,7 @@ fn stacktrace(input_dir: PathBuf) {
                     &handler,
                     &Options {
                         swcrc: true,
-                        is_module: true,
+                        is_module: IsModule::Bool(true),
                         source_maps: Some(SourceMapsConfig::Str("inline".to_string())),
                         ..Default::default()
                     },
diff --git a/crates/swc/tests/tsc.rs b/crates/swc/tests/tsc.rs
index 07a9602caa..c984434e55 100644
--- a/crates/swc/tests/tsc.rs
+++ b/crates/swc/tests/tsc.rs
@@ -6,7 +6,7 @@ use std::{
     path::{Path, PathBuf},
 };
 use swc::{
-    config::{Config, JscConfig, Options},
+    config::{Config, IsModule, JscConfig, Options},
     Compiler,
 };
 use swc_ecma_ast::EsVersion;
@@ -113,7 +113,7 @@ fn compile(input: &Path, output: &Path, opts: Options) {
                 fm,
                 &handler,
                 &Options {
-                    is_module: true,
+                    is_module: IsModule::Bool(true),
 
                     config: Config {
                         jsc: JscConfig {
diff --git a/crates/swc_babel_compat/tests/convert.rs b/crates/swc_babel_compat/tests/convert.rs
index 14515e2b70..101a43a5f0 100644
--- a/crates/swc_babel_compat/tests/convert.rs
+++ b/crates/swc_babel_compat/tests/convert.rs
@@ -9,7 +9,7 @@ use std::{
     path::{Path, PathBuf},
     sync::Arc,
 };
-use swc::Compiler;
+use swc::{config::IsModule, Compiler};
 use swc_babel_ast::File;
 use swc_babel_compat::babelify::{normalize::normalize, Babelify, Context};
 use swc_common::{
@@ -136,7 +136,7 @@ fn run_test(src: String, expected: String, syntax: Syntax, is_module: bool) {
             &handler,
             Default::default(), // EsVersion (ES version)
             syntax,
-            is_module,
+            IsModule::Bool(is_module),
             true, // parse_conmments
         )
         .unwrap();
diff --git a/node-swc/__tests__/is_module_unknown_test.js b/node-swc/__tests__/is_module_unknown_test.js
new file mode 100644
index 0000000000..7a0373cb61
--- /dev/null
+++ b/node-swc/__tests__/is_module_unknown_test.js
@@ -0,0 +1,16 @@
+const swc = require("../../");
+
+it("should detect script", () => {
+  const script = swc.parseSync(`const fs = require('fs');`, { isModule: "unknown" });
+  expect(script.type).toBe("Script");
+});
+
+it("should default to isModule: true", () => {
+  const script = swc.parseSync(`foo;`, {});
+  expect(script.type).toBe("Module");
+});
+
+it("should detect module", () => {
+  const script = swc.parseSync(`import fs from "fs";`, { isModule: "unknown" });
+  expect(script.type).toBe("Module");
+});