Repository: swc-project/swc (language: rust)

Issue:
`test_transform` should probably use `parse_program` instead of `parse_module`
### Describe the feature

The problem right now with `test_transform` is that, because it uses `parse_module` to parse code internally, visitors that use `visit_script`/`visit_mut_script`/`fold_script` will not work because these functions are never called due to the parsed code being a module (thanks to `apply_transform`): https://github.com/swc-project/swc/blob/b0c00b96f18c391bea89059130d08484e3ac3506/crates/swc_ecma_transforms_testing/src/lib.rs#L175

To fix this, we could make `test_transform` use `parse_program` instead, but this might introduce breaking changes. A `Program` can be either a `Module` or `Script`, but if we make `test_transform` use `parse_program` it might break existing unit tests (1st or 3rd party) that use `visit_module` instead of `visit_program` (or the other two functions). Some of the code related to this would also need to be changed, but that doesn't seem too big of an issue.

Overall, making `test_transform` would make people's lives a bit easier when making visitors that use specific functions like `visit_script` if they know they will never need to use the program or module functions, but I'm curious what the maintainers think about this as I'm not sure if I'm right.

### Babel plugin or link to the feature description

_No response_

### Additional context

_No response_


Discussion / hints:
Also just came to thought, I believe `visit_program` (and others) will also not work in unit tests because a `Module` is not a `Program`, as far as I know. So to my understanding right now, only `visit_module`/`visit_mut_module`/`fold_module` can be called from `test_transform`, while `visit_program` and `visit_script` can never be called, due to the parsed code always being a `Module`.

Fix patch:
diff --git a/.changeset/nine-trees-beam.md b/.changeset/nine-trees-beam.md
new file mode 100644
index 000000000000..0edf6ec8d613
--- /dev/null
+++ b/.changeset/nine-trees-beam.md
@@ -0,0 +1,6 @@
+---
+swc_ecma_transforms_testing: breaking
+swc_ecma_transforms_optimization: patch
+---
+
+feat(es/testing): Parse test code as a `Program` instead of a `Module`
diff --git a/crates/swc_ecma_transforms_optimization/src/inline_globals.rs b/crates/swc_ecma_transforms_optimization/src/inline_globals.rs
index 8ddd29273162..6d5e0a407cae 100644
--- a/crates/swc_ecma_transforms_optimization/src/inline_globals.rs
+++ b/crates/swc_ecma_transforms_optimization/src/inline_globals.rs
@@ -202,7 +202,7 @@ impl VisitMut for InlineGlobals {
 #[cfg(test)]
 mod tests {
     use swc_ecma_transforms_testing::{test, Tester};
-    use swc_ecma_utils::DropSpan;
+    use swc_ecma_utils::{DropSpan, StmtOrModuleItem};
 
     use super::*;
 
@@ -220,17 +220,23 @@ mod tests {
                 (*v).into()
             };
 
-            let mut v = tester
+            let v = tester
                 .apply_transform(
                     as_folder(DropSpan),
                     "global.js",
                     ::swc_ecma_parser::Syntax::default(),
+                    None,
                     &v,
                 )
                 .unwrap();
-            assert_eq!(v.body.len(), 1);
-            let v = match v.body.pop().unwrap() {
-                ModuleItem::Stmt(Stmt::Expr(ExprStmt { expr, .. })) => *expr,
+
+            let v = match v {
+                Program::Module(mut m) => m.body.pop().and_then(|x| x.into_stmt().ok()),
+                Program::Script(mut s) => s.body.pop(),
+            };
+            assert!(v.is_some());
+            let v = match v.unwrap() {
+                Stmt::Expr(ExprStmt { expr, .. }) => *expr,
                 _ => unreachable!(),
             };
 
diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/inlining/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/inlining/mod.rs
index 90341b5f7e43..2157c7ae14d6 100644
--- a/crates/swc_ecma_transforms_optimization/src/simplify/inlining/mod.rs
+++ b/crates/swc_ecma_transforms_optimization/src/simplify/inlining/mod.rs
@@ -468,19 +468,19 @@ impl VisitMut for Inlining<'_> {
         self.visit_with_child(ScopeKind::Cond, &mut stmt.alt);
     }
 
-    fn visit_mut_module_items(&mut self, items: &mut Vec<ModuleItem>) {
+    fn visit_mut_program(&mut self, program: &mut Program) {
         let _tracing = span!(Level::ERROR, "inlining", pass = self.pass).entered();
 
         let old_phase = self.phase;
 
         self.phase = Phase::Analysis;
-        items.visit_mut_children_with(self);
+        program.visit_mut_children_with(self);
 
         tracing::trace!("Switching to Inlining phase");
 
         // Inline
         self.phase = Phase::Inlining;
-        items.visit_mut_children_with(self);
+        program.visit_mut_children_with(self);
 
         self.phase = old_phase;
     }


Test patch:
diff --git a/crates/swc_ecma_transforms/tests/decorators.rs b/crates/swc_ecma_transforms/tests/decorators.rs
index de0063687985..03d0082d40cc 100644
--- a/crates/swc_ecma_transforms/tests/decorators.rs
+++ b/crates/swc_ecma_transforms/tests/decorators.rs
@@ -91,6 +91,7 @@ fn transformation(t: &Tester) -> impl Fold {
 
 // transformation_declaration
 test!(
+    module,
     syntax(false),
     |t| transformation(t),
     transformation_declaration,
@@ -101,6 +102,7 @@ class A {}
 );
 // transformation_initialize_after_super_multiple
 test!(
+    module,
     syntax(false),
     |t| transformation(t),
     transformation_initialize_after_super_multiple,
@@ -134,6 +136,7 @@ export default @dec() class {}
 );
 // transformation_initialize_after_super_statement
 test!(
+    module,
     syntax(false),
     |t| transformation(t),
     transformation_initialize_after_super_statement,
@@ -211,6 +214,7 @@ expect(A).toBe(C);
 );
 // misc_method_name_not_shadow
 test!(
+    module,
     syntax(false),
     |t| tr(t),
     misc_method_name_not_shadow,
@@ -320,6 +324,7 @@ expect(() => {
 );
 // duplicated_keys_computed_keys_same_value
 test!(
+    module,
     syntax(false),
     |t| tr(t),
     duplicated_keys_computed_keys_same_value,
@@ -395,6 +400,7 @@ expect(log).toEqual(numsFrom0to9);
 );
 // transformation_initializer_after_super_bug_8808
 test!(
+    module,
     syntax(false),
     |t| transformation(t),
     transformation_initiailzer_after_super_bug_8808,
@@ -434,6 +440,7 @@ expect(A.prototype.method()).toBe(2);
 );
 // transformation_arguments
 test!(
+    module,
     syntax(false),
     |t| transformation(t),
     transformation_arguments,
@@ -532,6 +539,7 @@ expect(calls).toBe(1);
 // ordering
 // transformation_initialize_after_super_expression
 test!(
+    module,
     syntax(false),
     |t| transformation(t),
     transformation_initialize_after_super_expression,
@@ -903,6 +911,7 @@ expect(Object.getOwnPropertyDescriptor(A.prototype, "foo")).toEqual({
 );
 // transformation_extends
 test!(
+    module,
     syntax(false),
     |t| transformation(t),
     transformation_extends,
@@ -914,6 +923,7 @@ test!(
 // finishers
 // transformation_extends_await
 test!(
+    module,
     syntax(false),
     |t| transformation(t),
     transformation_extends_await,
@@ -926,6 +936,7 @@ async function g() {
 );
 // transformation_extends_yield
 test!(
+    module,
     syntax(false),
     |t| transformation(t),
     transformation_extends_yield,
@@ -1092,6 +1103,7 @@ expect(i).toBe(2);
 );
 // transformation_initialize_after_super_bug_8931
 test!(
+    module,
     syntax(false),
     |t| transformation(t),
     transformation_initialize_after_super_bug_8931,
@@ -1318,6 +1330,7 @@ expect(Foo.prototype.bar).toBe(value2);
 );
 // transformation_expression
 test!(
+    module,
     syntax(false),
     |t| transformation(t),
     transformation_expression,
@@ -1354,6 +1367,7 @@ expect(A.method()).toBe(2);
 // element_descriptors
 // duplicated_keys_computed_keys_same_ast
 test!(
+    module,
     syntax(false),
     |t| tr(t),
     duplicated_keys_computed_keys_same_ast,
@@ -3882,6 +3896,7 @@ eval: function () {
 );
 
 test!(
+    module,
     ts(),
     |_| decorators(Default::default()),
     issue_846_1,
diff --git a/crates/swc_ecma_transforms_module/tests/amd.rs b/crates/swc_ecma_transforms_module/tests/amd.rs
index 674a16c1307e..1fd4b111813f 100644
--- a/crates/swc_ecma_transforms_module/tests/amd.rs
+++ b/crates/swc_ecma_transforms_module/tests/amd.rs
@@ -5,7 +5,7 @@ use swc_ecma_parser::{Syntax, TsSyntax};
 use swc_ecma_transforms_base::{feature::FeatureFlag, resolver};
 use swc_ecma_transforms_compat::es2015::for_of;
 use swc_ecma_transforms_module::amd::{self, amd};
-use swc_ecma_transforms_testing::{test, test_fixture};
+use swc_ecma_transforms_testing::{test, test_fixture, FixtureTestConfig};
 use swc_ecma_transforms_typescript::typescript;
 use swc_ecma_visit::Fold;
 
@@ -65,11 +65,15 @@ fn esm_to_amd(input: PathBuf) {
         &|t| tr(config.clone(), is_ts, t.comments.clone()),
         &input,
         &output,
-        Default::default(),
+        FixtureTestConfig {
+            module: Some(true),
+            ..Default::default()
+        },
     );
 }
 
 test!(
+    module,
     syntax(),
     |t| chain!(
         for_of(for_of::Config {
diff --git a/crates/swc_ecma_transforms_module/tests/common_js.rs b/crates/swc_ecma_transforms_module/tests/common_js.rs
index 6857f9fe44dc..65e7abe90439 100644
--- a/crates/swc_ecma_transforms_module/tests/common_js.rs
+++ b/crates/swc_ecma_transforms_module/tests/common_js.rs
@@ -59,12 +59,14 @@ fn esm_to_cjs(input: PathBuf) {
         &output,
         FixtureTestConfig {
             sourcemap: false,
+            module: Some(true),
             ..Default::default()
         },
     );
 }
 
 test!(
+    module,
     syntax(),
     |_| chain!(
         for_of(for_of::Config {
diff --git a/crates/swc_ecma_transforms_module/tests/path_node.rs b/crates/swc_ecma_transforms_module/tests/path_node.rs
index 435e648e4ce3..a80a1d43a149 100644
--- a/crates/swc_ecma_transforms_module/tests/path_node.rs
+++ b/crates/swc_ecma_transforms_module/tests/path_node.rs
@@ -12,7 +12,7 @@ use swc_ecma_transforms_module::{
     path::{ImportResolver, NodeImportResolver},
     rewriter::import_rewriter,
 };
-use swc_ecma_transforms_testing::test_fixture;
+use swc_ecma_transforms_testing::{test_fixture, FixtureTestConfig};
 use testing::run_test2;
 
 type TestProvider = NodeImportResolver<NodeModulesResolver>;
@@ -81,7 +81,10 @@ fn issue_4730() {
         },
         &input_dir.join("src").join("index.js"),
         &output_dir.join("index.js"),
-        Default::default(),
+        FixtureTestConfig {
+            module: Some(true),
+            ..Default::default()
+        },
     );
 }
 
@@ -147,6 +150,9 @@ fn fixture(input_dir: PathBuf) {
         },
         &index_path,
         &output_dir.join("index.ts"),
-        Default::default(),
+        FixtureTestConfig {
+            module: Some(true),
+            ..Default::default()
+        },
     );
 }
diff --git a/crates/swc_ecma_transforms_module/tests/system_js.rs b/crates/swc_ecma_transforms_module/tests/system_js.rs
index d07e2c4425dc..98706027c11f 100644
--- a/crates/swc_ecma_transforms_module/tests/system_js.rs
+++ b/crates/swc_ecma_transforms_module/tests/system_js.rs
@@ -6,7 +6,7 @@ use swc_common::{chain, Mark};
 use swc_ecma_parser::Syntax;
 use swc_ecma_transforms_base::resolver;
 use swc_ecma_transforms_module::system_js::{system_js, Config};
-use swc_ecma_transforms_testing::{test, test_fixture, Tester};
+use swc_ecma_transforms_testing::{test, test_fixture, FixtureTestConfig, Tester};
 use swc_ecma_visit::Fold;
 
 fn syntax() -> Syntax {
@@ -23,6 +23,7 @@ fn tr(_tester: &mut Tester<'_>, config: Config) -> impl Fold {
 }
 
 test!(
+    module,
     syntax(),
     |tester| tr(tester, Default::default()),
     allow_continuous_assignment,
@@ -30,6 +31,7 @@ test!(
 );
 
 test!(
+    module,
     syntax(),
     |tester| tr(
         tester,
@@ -43,6 +45,7 @@ test!(
 );
 
 test!(
+    module,
     syntax(),
     |tester| tr(
         tester,
@@ -60,6 +63,7 @@ test!(
 );
 
 test!(
+    module,
     syntax(),
     |tester| tr(
         tester,
@@ -82,6 +86,7 @@ test!(
 );
 
 test!(
+    module,
     syntax(),
     |tester| tr(
         tester,
@@ -99,6 +104,7 @@ test!(
 );
 
 test!(
+    module,
     syntax(),
     |tester| tr(tester, Default::default()),
     imports,
@@ -122,6 +128,9 @@ fn fixture(input: PathBuf) {
         &|tester| tr(tester, Default::default()),
         &input,
         &output,
-        Default::default(),
+        FixtureTestConfig {
+            module: Some(true),
+            ..Default::default()
+        },
     );
 }
diff --git a/crates/swc_ecma_transforms_module/tests/umd.rs b/crates/swc_ecma_transforms_module/tests/umd.rs
index 0512aa30635f..6d1cbaec3227 100644
--- a/crates/swc_ecma_transforms_module/tests/umd.rs
+++ b/crates/swc_ecma_transforms_module/tests/umd.rs
@@ -4,7 +4,7 @@ use swc_common::{chain, Mark};
 use swc_ecma_parser::{Syntax, TsSyntax};
 use swc_ecma_transforms_base::{feature::FeatureFlag, resolver};
 use swc_ecma_transforms_module::umd::{umd, Config};
-use swc_ecma_transforms_testing::{test_fixture, Tester};
+use swc_ecma_transforms_testing::{test_fixture, FixtureTestConfig, Tester};
 use swc_ecma_transforms_typescript::typescript;
 use swc_ecma_visit::Fold;
 
@@ -63,6 +63,9 @@ fn esm_to_umd(input: PathBuf) {
         &|tester| tr(tester, config.clone(), is_ts),
         &input,
         &output,
-        Default::default(),
+        FixtureTestConfig {
+            module: Some(true),
+            ..Default::default()
+        },
     );
 }
diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/branch/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/branch/tests.rs
index c05585ed7c00..3844b6cbf409 100644
--- a/crates/swc_ecma_transforms_optimization/src/simplify/branch/tests.rs
+++ b/crates/swc_ecma_transforms_optimization/src/simplify/branch/tests.rs
@@ -9,6 +9,7 @@ macro_rules! test_stmt {
     ($l:expr, $r:expr) => {
         swc_ecma_transforms_testing::test_transform(
             ::swc_ecma_parser::Syntax::default(),
+            None,
             |_| {
                 let unresolved_mark = Mark::new();
                 let top_level_mark = Mark::new();
@@ -30,7 +31,6 @@ macro_rules! test_stmt {
             },
             $l,
             $r,
-            true,
         )
     };
 }
diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs
index dc2bae680324..89ace2edaced 100644
--- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs
+++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs
@@ -9,6 +9,7 @@ use super::SimplifyExpr;
 fn fold(src: &str, expected: &str) {
     test_transform(
         ::swc_ecma_parser::Syntax::default(),
+        None,
         |_| {
             let unresolved_mark = Mark::new();
             let top_level_mark = Mark::new();
@@ -32,7 +33,6 @@ fn fold(src: &str, expected: &str) {
         },
         src,
         expected,
-        true,
     )
 }
 
diff --git a/crates/swc_ecma_transforms_optimization/tests/simplify.rs b/crates/swc_ecma_transforms_optimization/tests/simplify.rs
index 2c694b01f109..3a8ee1291d3c 100644
--- a/crates/swc_ecma_transforms_optimization/tests/simplify.rs
+++ b/crates/swc_ecma_transforms_optimization/tests/simplify.rs
@@ -19,6 +19,7 @@ use swc_ecma_transforms_typescript::strip;
 fn test(src: &str, expected: &str) {
     test_transform(
         ::swc_ecma_parser::Syntax::default(),
+        None,
         |_| {
             let unresolved_mark = Mark::new();
             let top_level_mark = Mark::new();
@@ -35,7 +36,6 @@ fn test(src: &str, expected: &str) {
         },
         src,
         expected,
-        true,
     )
 }
 
diff --git a/crates/swc_ecma_transforms_optimization/tests/simplify_inlining.rs b/crates/swc_ecma_transforms_optimization/tests/simplify_inlining.rs
index ad1f9e59ec53..14359428850c 100644
--- a/crates/swc_ecma_transforms_optimization/tests/simplify_inlining.rs
+++ b/crates/swc_ecma_transforms_optimization/tests/simplify_inlining.rs
@@ -57,6 +57,7 @@ macro_rules! identical {
 fn test(src: &str, expected: &str) {
     swc_ecma_transforms_testing::test_transform(
         ::swc_ecma_parser::Syntax::default(),
+        None,
         |_| {
             let unresolved_mark = Mark::new();
             let top_level_mark = Mark::new();
@@ -68,7 +69,6 @@ fn test(src: &str, expected: &str) {
         },
         src,
         expected,
-        true,
     )
 }
 
diff --git a/crates/swc_ecma_transforms_proposal/tests/decorator-tests b/crates/swc_ecma_transforms_proposal/tests/decorator-tests
index 8e9c0b0fb3d5..a03c69227413 160000
--- a/crates/swc_ecma_transforms_proposal/tests/decorator-tests
+++ b/crates/swc_ecma_transforms_proposal/tests/decorator-tests
@@ -1,1 +1,1 @@
-Subproject commit 8e9c0b0fb3d548f378420aabbd351087efb5d5e5
+Subproject commit a03c69227413dfcac8a1e9f89d93554fa3b8b7fe
diff --git a/crates/swc_ecma_transforms_proposal/tests/decorators.rs b/crates/swc_ecma_transforms_proposal/tests/decorators.rs
index fe10f8bbf0b6..82fd7340f8de 100644
--- a/crates/swc_ecma_transforms_proposal/tests/decorators.rs
+++ b/crates/swc_ecma_transforms_proposal/tests/decorators.rs
@@ -76,6 +76,7 @@ fn fixture_inner(input: PathBuf) {
         &output,
         FixtureTestConfig {
             allow_error: true,
+            module: Some(true),
             ..Default::default()
         },
     );
diff --git a/crates/swc_ecma_transforms_react/src/jsx/tests.rs b/crates/swc_ecma_transforms_react/src/jsx/tests.rs
index c7461a3eba45..66541a384d34 100644
--- a/crates/swc_ecma_transforms_react/src/jsx/tests.rs
+++ b/crates/swc_ecma_transforms_react/src/jsx/tests.rs
@@ -108,6 +108,7 @@ fn integration_tr(t: &mut Tester, mut options: FixtureOptions) -> impl Fold {
     )
 }
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -122,6 +123,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -140,6 +142,7 @@ var bar = function () {
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -164,6 +167,7 @@ var x =
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -179,6 +183,7 @@ Component = React.createClass({
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -195,6 +200,7 @@ export default React.createClass({
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -219,6 +225,7 @@ var Bar = React.createClass({
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -236,6 +243,7 @@ exports = {
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -252,6 +260,7 @@ exports.Component = React.createClass({
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -268,6 +277,7 @@ var Component = React.createClass({
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -283,6 +293,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -300,6 +311,7 @@ var profile = <div>
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -319,6 +331,7 @@ var profile = <div>
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -343,6 +356,7 @@ var profile = <div>
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -353,6 +367,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -393,6 +408,7 @@ class App extends React.Component {
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -406,6 +422,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -416,6 +433,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -426,6 +444,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -436,6 +455,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -446,6 +466,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -469,6 +490,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -483,6 +505,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -498,6 +521,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -524,6 +548,7 @@ var x = <Composite>
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -534,6 +559,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -544,6 +570,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -558,6 +585,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -579,6 +607,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -591,6 +620,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -615,6 +645,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -637,6 +668,7 @@ React.render(<HelloMessage name={
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -647,6 +679,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -663,6 +696,7 @@ var x = <div>
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -690,6 +724,7 @@ var x =
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -700,6 +735,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -710,6 +746,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -720,6 +757,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -730,6 +768,7 @@ test!(
 );
 
 test!(
+    module,
     // Comments are currently stripped out
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
@@ -752,6 +791,7 @@ var x = (
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -762,6 +802,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -780,6 +821,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -790,6 +832,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -803,6 +846,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -813,6 +857,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -823,6 +868,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -833,6 +879,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -843,6 +890,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -853,6 +901,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -864,6 +913,7 @@ const b = <div>test</div>"
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -879,6 +929,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -890,6 +941,7 @@ test!(
 
 // https://github.com/swc-project/swc/issues/517
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -912,6 +964,7 @@ fn jsx_text() {
 
 // https://github.com/swc-project/swc/issues/542
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -966,6 +1019,7 @@ return (
 );
 
 test!(
+    module,
     Syntax::Es(EsSyntax {
         jsx: true,
         ..Default::default()
@@ -1011,6 +1065,7 @@ fn fixture(input: PathBuf) {
         &output,
         FixtureTestConfig {
             allow_error: true,
+            module: Some(true),
             ..Default::default()
         },
     );
@@ -1036,6 +1091,7 @@ fn integration(input: PathBuf) {
         &output,
         FixtureTestConfig {
             allow_error: true,
+            module: Some(true),
             ..Default::default()
         },
     );
diff --git a/crates/swc_ecma_transforms_react/src/jsx_self/tests.rs b/crates/swc_ecma_transforms_react/src/jsx_self/tests.rs
index ad6b332311af..1315aecf2a64 100644
--- a/crates/swc_ecma_transforms_react/src/jsx_self/tests.rs
+++ b/crates/swc_ecma_transforms_react/src/jsx_self/tests.rs
@@ -7,6 +7,7 @@ fn tr() -> impl Fold {
 }
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
diff --git a/crates/swc_ecma_transforms_react/src/jsx_src/tests.rs b/crates/swc_ecma_transforms_react/src/jsx_src/tests.rs
index f20fa8cb082e..9523617842f0 100644
--- a/crates/swc_ecma_transforms_react/src/jsx_src/tests.rs
+++ b/crates/swc_ecma_transforms_react/src/jsx_src/tests.rs
@@ -35,6 +35,7 @@ expect(actual).toBe(expected);
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
diff --git a/crates/swc_ecma_transforms_react/src/refresh/tests.rs b/crates/swc_ecma_transforms_react/src/refresh/tests.rs
index f8012a87d33e..8d5c63c1d332 100644
--- a/crates/swc_ecma_transforms_react/src/refresh/tests.rs
+++ b/crates/swc_ecma_transforms_react/src/refresh/tests.rs
@@ -25,6 +25,7 @@ fn tr(t: &mut Tester) -> impl Fold {
 }
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -43,6 +44,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -68,6 +70,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -89,6 +92,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -105,6 +109,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -119,6 +124,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -140,6 +146,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -160,6 +167,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -186,6 +194,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -209,6 +218,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -219,6 +229,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -239,6 +250,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -253,6 +265,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -267,6 +280,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -283,6 +297,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -301,6 +316,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -336,6 +352,7 @@ test!(
 // A doesn't get registered because it's not declared locally.
 // Alias doesn't get registered because its definition is just an identifier.
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -393,6 +410,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -410,6 +428,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -426,6 +445,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -451,6 +471,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -480,6 +501,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -496,6 +518,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -513,6 +536,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -536,6 +560,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Typescript(::swc_ecma_parser::TsSyntax {
         tsx: true,
         ..Default::default()
@@ -578,6 +603,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -602,6 +628,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Typescript(::swc_ecma_parser::TsSyntax {
         tsx: true,
         ..Default::default()
@@ -629,6 +656,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -652,6 +680,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -668,6 +697,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -701,6 +731,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Typescript(::swc_ecma_parser::TsSyntax {
         tsx: true,
         ..Default::default()
@@ -717,6 +748,7 @@ test!(
 );
 
 test!(
+    module,
     Default::default(),
     tr,
     next_001,
@@ -728,6 +760,7 @@ test!(
 );
 
 test!(
+    module,
     Default::default(),
     tr,
     issue_2261,
@@ -741,6 +774,7 @@ test!(
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
@@ -759,6 +793,7 @@ const a = (a) => {
 );
 
 test!(
+    module,
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsSyntax {
         jsx: true,
         ..Default::default()
diff --git a/crates/swc_ecma_transforms_testing/src/lib.rs b/crates/swc_ecma_transforms_testing/src/lib.rs
index 9bda5e768e12..251d4943af27 100644
--- a/crates/swc_ecma_transforms_testing/src/lib.rs
+++ b/crates/swc_ecma_transforms_testing/src/lib.rs
@@ -167,37 +167,26 @@ impl<'a> Tester<'a> {
         mut tr: T,
         name: &str,
         syntax: Syntax,
+        is_module: Option<bool>,
         src: &str,
-    ) -> Result<Module, ()> {
-        let fm = self
-            .cm
-            .new_source_file(FileName::Real(name.into()).into(), src.into());
-
-        let module = {
-            let mut p = Parser::new_from(Lexer::new(
+    ) -> Result<Program, ()> {
+        let program =
+            self.with_parser(
+                name,
                 syntax,
-                EsVersion::latest(),
-                StringInput::from(&*fm),
-                Some(&self.comments),
-            ));
-            let res = p
-                .parse_module()
-                .map_err(|e| e.into_diagnostic(self.handler).emit());
-
-            for e in p.take_errors() {
-                e.into_diagnostic(self.handler).emit()
-            }
-
-            res?
-        };
-
-        let module = Program::Module(module).fold_with(&mut tr);
-
-        Ok(module.expect_module())
+                src,
+                |parser: &mut Parser<Lexer>| match is_module {
+                    Some(true) => parser.parse_module().map(Program::Module),
+                    Some(false) => parser.parse_script().map(Program::Script),
+                    None => parser.parse_program(),
+                },
+            )?;
+
+        Ok(program.fold_with(&mut tr))
     }
 
-    pub fn print(&mut self, module: &Module, comments: &Rc<SingleThreadedComments>) -> String {
-        to_code_default(self.cm.clone(), Some(comments), module)
+    pub fn print(&mut self, program: &Program, comments: &Rc<SingleThreadedComments>) -> String {
+        to_code_default(self.cm.clone(), Some(comments), program)
     }
 }
 
@@ -259,10 +248,10 @@ where
 #[track_caller]
 pub fn test_transform<F, P>(
     syntax: Syntax,
+    is_module: Option<bool>,
     tr: F,
     input: &str,
     expected: &str,
-    _always_ok_if_code_eq: bool,
 ) where
     F: FnOnce(&mut Tester) -> P,
     P: Fold,
@@ -272,6 +261,7 @@ pub fn test_transform<F, P>(
             as_folder(::swc_ecma_utils::DropSpan),
             "output.js",
             syntax,
+            is_module,
             expected,
         )?;
 
@@ -280,7 +270,7 @@ pub fn test_transform<F, P>(
         println!("----- Actual -----");
 
         let tr = make_tr(tr, tester);
-        let actual = tester.apply_transform(tr, "input.js", syntax, input)?;
+        let actual = tester.apply_transform(tr, "input.js", syntax, is_module, input)?;
 
         match ::std::env::var("PRINT_HYGIENE") {
             Ok(ref s) if s == "1" => {
@@ -340,8 +330,13 @@ pub fn test_transform<F, P>(
 /// NOT A PUBLIC API. DO NOT USE.
 #[doc(hidden)]
 #[track_caller]
-pub fn test_inline_input_output<F, P>(syntax: Syntax, tr: F, input: &str, output: &str)
-where
+pub fn test_inline_input_output<F, P>(
+    syntax: Syntax,
+    is_module: Option<bool>,
+    tr: F,
+    input: &str,
+    output: &str,
+) where
     F: FnOnce(&mut Tester) -> P,
     P: Fold,
 {
@@ -350,9 +345,10 @@ where
     let expected = output;
 
     let expected_src = Tester::run(|tester| {
-        let expected_module = tester.apply_transform(noop(), "expected.js", syntax, expected)?;
+        let expected_program =
+            tester.apply_transform(noop(), "expected.js", syntax, is_module, expected)?;
 
-        let expected_src = tester.print(&expected_module, &Default::default());
+        let expected_src = tester.print(&expected_program, &Default::default());
 
         println!(
             "----- {} -----\n{}",
@@ -370,7 +366,7 @@ where
 
         println!("----- {} -----", Color::Green.paint("Actual"));
 
-        let actual = tester.apply_transform(tr, "input.js", syntax, input)?;
+        let actual = tester.apply_transform(tr, "input.js", syntax, is_module, input)?;
 
         match ::std::env::var("PRINT_HYGIENE") {
             Ok(ref s) if s == "1" => {
@@ -407,8 +403,13 @@ where
 /// NOT A PUBLIC API. DO NOT USE.
 #[doc(hidden)]
 #[track_caller]
-pub fn test_inlined_transform<F, P>(test_name: &str, syntax: Syntax, tr: F, input: &str)
-where
+pub fn test_inlined_transform<F, P>(
+    test_name: &str,
+    syntax: Syntax,
+    module: Option<bool>,
+    tr: F,
+    input: &str,
+) where
     F: FnOnce(&mut Tester) -> P,
     P: Fold,
 {
@@ -429,7 +430,10 @@ where
         Box::new(move |tester| Box::new(tr(tester))),
         input,
         &snapshot_dir.join(format!("{test_name}.js")),
-        Default::default(),
+        FixtureTestConfig {
+            module,
+            ..Default::default()
+        },
     )
 }
 
@@ -448,14 +452,14 @@ macro_rules! test_inline {
         #[test]
         #[ignore]
         fn $test_name() {
-            $crate::test_inline_input_output($syntax, $tr, $input, $output)
+            $crate::test_inline_input_output($syntax, None, $tr, $input, $output)
         }
     };
 
     ($syntax:expr, $tr:expr, $test_name:ident, $input:expr, $output:expr) => {
         #[test]
         fn $test_name() {
-            $crate::test_inline_input_output($syntax, $tr, $input, $output)
+            $crate::test_inline_input_output($syntax, None, $tr, $input, $output)
         }
     };
 }
@@ -480,7 +484,7 @@ test_inline!(
 #[test]
 #[should_panic]
 fn test_inline_should_fail() {
-    test_inline_input_output(Default::default(), |_| noop(), "class Foo {}", "");
+    test_inline_input_output(Default::default(), None, |_| noop(), "class Foo {}", "");
 }
 
 #[macro_export]
@@ -489,21 +493,41 @@ macro_rules! test {
         #[test]
         #[ignore]
         fn $test_name() {
-            $crate::test_inlined_transform(stringify!($test_name), $syntax, $tr, $input)
+            $crate::test_inlined_transform(stringify!($test_name), $syntax, None, $tr, $input)
         }
     };
 
     ($syntax:expr, $tr:expr, $test_name:ident, $input:expr) => {
         #[test]
         fn $test_name() {
-            $crate::test_inlined_transform(stringify!($test_name), $syntax, $tr, $input)
+            $crate::test_inlined_transform(stringify!($test_name), $syntax, None, $tr, $input)
+        }
+    };
+
+    (module, $syntax:expr, $tr:expr, $test_name:ident, $input:expr) => {
+        #[test]
+        fn $test_name() {
+            $crate::test_inlined_transform(stringify!($test_name), $syntax, Some(true), $tr, $input)
+        }
+    };
+
+    (script, $syntax:expr, $tr:expr, $test_name:ident, $input:expr) => {
+        #[test]
+        fn $test_name() {
+            $crate::test_inlined_script_transform(
+                stringify!($test_name),
+                $syntax,
+                Some(false),
+                $tr,
+                $input,
+            )
         }
     };
 
     ($syntax:expr, $tr:expr, $test_name:ident, $input:expr, ok_if_code_eq) => {
         #[test]
         fn $test_name() {
-            $crate::test_inlined_transform(stringify!($test_name), $syntax, $tr, $input)
+            $crate::test_inlined_transform(stringify!($test_name), $syntax, None, $tr, $input)
         }
     };
 }
@@ -518,12 +542,12 @@ where
     Tester::run(|tester| {
         let tr = make_tr(tr, tester);
 
-        let module = tester.apply_transform(tr, "input.js", syntax, input)?;
+        let program = tester.apply_transform(tr, "input.js", syntax, Some(true), input)?;
 
         match ::std::env::var("PRINT_HYGIENE") {
             Ok(ref s) if s == "1" => {
                 let hygiene_src = tester.print(
-                    &module.clone().fold_with(&mut HygieneVisualizer),
+                    &program.clone().fold_with(&mut HygieneVisualizer),
                     &tester.comments.clone(),
                 );
                 println!("----- Hygiene -----\n{}", hygiene_src);
@@ -531,14 +555,14 @@ where
             _ => {}
         }
 
-        let mut module = module
+        let mut program = program
             .fold_with(&mut hygiene::hygiene())
             .fold_with(&mut fixer::fixer(Some(&tester.comments)));
 
-        let src_without_helpers = tester.print(&module, &tester.comments.clone());
-        module = module.fold_with(&mut inject_helpers(Mark::fresh(Mark::root())));
+        let src_without_helpers = tester.print(&program, &tester.comments.clone());
+        program = program.fold_with(&mut inject_helpers(Mark::fresh(Mark::root())));
 
-        let transformed_src = tester.print(&module, &tester.comments.clone());
+        let transformed_src = tester.print(&program, &tester.comments.clone());
 
         println!(
             "\t>>>>> Orig <<<<<\n{}\n\t>>>>> Code <<<<<\n{}",
@@ -566,10 +590,11 @@ where
     Tester::run(|tester| {
         let tr = make_tr(tr, tester);
 
-        let module = tester.apply_transform(
+        let program = tester.apply_transform(
             tr,
             "input.js",
             syntax,
+            Some(true),
             &format!(
                 "it('should work', async function () {{
                     {}
@@ -580,7 +605,7 @@ where
         match ::std::env::var("PRINT_HYGIENE") {
             Ok(ref s) if s == "1" => {
                 let hygiene_src = tester.print(
-                    &module.clone().fold_with(&mut HygieneVisualizer),
+                    &program.clone().fold_with(&mut HygieneVisualizer),
                     &tester.comments.clone(),
                 );
                 println!("----- Hygiene -----\n{}", hygiene_src);
@@ -588,14 +613,14 @@ where
             _ => {}
         }
 
-        let mut module = module
+        let mut program = program
             .fold_with(&mut hygiene::hygiene())
             .fold_with(&mut fixer::fixer(Some(&tester.comments)));
 
-        let src_without_helpers = tester.print(&module, &tester.comments.clone());
-        module = module.fold_with(&mut inject_helpers(Mark::fresh(Mark::root())));
+        let src_without_helpers = tester.print(&program, &tester.comments.clone());
+        program = program.fold_with(&mut inject_helpers(Mark::fresh(Mark::root())));
 
-        let src = tester.print(&module, &tester.comments.clone());
+        let src = tester.print(&program, &tester.comments.clone());
 
         println!(
             "\t>>>>> {} <<<<<\n{}\n\t>>>>> {} <<<<<\n{}",
@@ -693,6 +718,12 @@ fn stdout_of(code: &str) -> Result<String, Error> {
 /// Test transformation.
 #[macro_export]
 macro_rules! test_exec {
+    (@check) => {
+        if ::std::env::var("EXEC").unwrap_or(String::from("")) == "0" {
+            return;
+        }
+    };
+
     (ignore, $syntax:expr, $tr:expr, $test_name:ident, $input:expr) => {
         #[test]
         #[ignore]
@@ -704,10 +735,7 @@ macro_rules! test_exec {
     ($syntax:expr, $tr:expr, $test_name:ident, $input:expr) => {
         #[test]
         fn $test_name() {
-            if ::std::env::var("EXEC").unwrap_or(String::from("")) == "0" {
-                return;
-            }
-
+            test_exec!(@check);
             $crate::exec_tr(stringify!($test_name), $syntax, $tr, $input)
         }
     };
@@ -817,7 +845,15 @@ pub struct FixtureTestConfig {
     ///
     /// Defaults to false.
     pub allow_error: bool,
+
+    /// Determines what type of [Program] the source code is parsed as.
+    ///
+    /// - `Some(true)`: parsed as a [Program::Module]
+    /// - `Some(false)`: parsed as a [Program::Script]
+    /// - `None`: parsed as a [Program] (underlying type is auto-detected)
+    pub module: Option<bool>,
 }
+
 /// You can do `UPDATE=1 cargo test` to update fixtures.
 pub fn test_fixture<P>(
     syntax: Syntax,
@@ -853,9 +889,10 @@ fn test_fixture_inner<'a>(
     let expected = expected.unwrap_or_default();
 
     let expected_src = Tester::run(|tester| {
-        let expected_module = tester.apply_transform(noop(), "expected.js", syntax, &expected)?;
+        let expected_program =
+            tester.apply_transform(noop(), "expected.js", syntax, config.module, &expected)?;
 
-        let expected_src = tester.print(&expected_module, &tester.comments.clone());
+        let expected_src = tester.print(&expected_program, &tester.comments.clone());
 
         println!(
             "----- {} -----\n{}",
@@ -881,7 +918,7 @@ fn test_fixture_inner<'a>(
 
         eprintln!("----- {} -----", Color::Green.paint("Actual"));
 
-        let actual = tester.apply_transform(tr, "input.js", syntax, input)?;
+        let actual = tester.apply_transform(tr, "input.js", syntax, config.module, input)?;
 
         eprintln!("----- {} -----", Color::Green.paint("Comments"));
         eprintln!("{:?}", tester.comments);
diff --git a/crates/swc_ecma_transforms_testing/tests/visited.rs b/crates/swc_ecma_transforms_testing/tests/visited.rs
index d11c82f9377a..6621303e0aeb 100644
--- a/crates/swc_ecma_transforms_testing/tests/visited.rs
+++ b/crates/swc_ecma_transforms_testing/tests/visited.rs
@@ -26,6 +26,7 @@ fn ensure_visited() {
             jsx: true,
             ..Default::default()
         }),
+        None,
         |_| Panicking,
         "
         import React from 'react';
@@ -35,6 +36,5 @@ fn ensure_visited() {
         import React from 'react';
         const comp = () => <amp-something className='something' />;
         ",
-        false,
     );
 }