diff --git a/cli/ast/mod.rs b/cli/ast/mod.rs index 7b49bbedb6..428fe126d0 100644 --- a/cli/ast/mod.rs +++ b/cli/ast/mod.rs @@ -314,6 +314,7 @@ impl ParsedModule { let mut passes = chain!( Optional::new(jsx_pass, options.transform_jsx), Optional::new(transforms::DownlevelImportsFolder, options.repl_imports), + Optional::new(transforms::StripExportsFolder, options.repl_imports), proposals::decorators::decorators(proposals::decorators::Config { legacy: true, emit_metadata: options.emit_metadata diff --git a/cli/ast/transforms.rs b/cli/ast/transforms.rs index 570a0bf5b4..461a12ff48 100644 --- a/cli/ast/transforms.rs +++ b/cli/ast/transforms.rs @@ -21,33 +21,12 @@ impl Fold for DownlevelImportsFolder { ModuleItem::ModuleDecl(ModuleDecl::Import(import_decl)) => { // Handle type only imports if import_decl.type_only { - return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP })); + // should have no side effects + return create_empty_stmt(); } // The initializer (ex. `await import('./mod.ts')`) - let initializer = Box::new(Expr::Await(AwaitExpr { - span: DUMMY_SP, - arg: Box::new(Expr::Call(CallExpr { - span: DUMMY_SP, - callee: ExprOrSuper::Expr(Box::new(Expr::Ident(Ident { - span: DUMMY_SP, - sym: "import".into(), - optional: false, - }))), - args: vec![ExprOrSpread { - spread: None, - expr: Box::new(Expr::Lit(Lit::Str(Str { - span: DUMMY_SP, - has_escape: false, - kind: StrKind::Normal { - contains_quote: false, - }, - value: import_decl.src.value.clone(), - }))), - }], - type_args: None, - })), - })); + let initializer = create_await_import_expr(&import_decl.src.value); // Handle imports for the side effects // ex. `import "module.ts"` -> `await import("module.ts");` @@ -128,6 +107,78 @@ impl Fold for DownlevelImportsFolder { } } +/// Strips export declarations and exports on named exports for the REPL. +pub struct StripExportsFolder; + +impl Fold for StripExportsFolder { + noop_fold_type!(); // skip typescript specific nodes + + fn fold_module_item( + &mut self, + module_item: swc_ast::ModuleItem, + ) -> swc_ast::ModuleItem { + use swc_ecmascript::ast::*; + + match module_item { + ModuleItem::ModuleDecl(ModuleDecl::ExportAll(export_all)) => { + ModuleItem::Stmt(Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: create_await_import_expr(&export_all.src.value), + })) + } + ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export_named)) => { + if let Some(src) = export_named.src { + ModuleItem::Stmt(Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: create_await_import_expr(&src.value), + })) + } else { + create_empty_stmt() + } + } + ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(default_expr)) => { + // transform a default export expression to its expression + ModuleItem::Stmt(Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: default_expr.expr, + })) + } + ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => { + // strip the export keyword on an exported declaration + ModuleItem::Stmt(Stmt::Decl(export_decl.decl)) + } + ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(default_decl)) => { + // only keep named default exports + match default_decl.decl { + DefaultDecl::Fn(FnExpr { + ident: Some(ident), + function, + }) => ModuleItem::Stmt(Stmt::Decl(Decl::Fn(FnDecl { + declare: false, + ident, + function, + }))), + DefaultDecl::Class(ClassExpr { + ident: Some(ident), + class, + }) => ModuleItem::Stmt(Stmt::Decl(Decl::Class(ClassDecl { + declare: false, + ident, + class, + }))), + _ => create_empty_stmt(), + } + } + _ => module_item, + } + } +} + +fn create_empty_stmt() -> swc_ast::ModuleItem { + use swc_ast::*; + ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP })) +} + fn create_binding_ident(name: String) -> swc_ast::BindingIdent { swc_ast::BindingIdent { id: create_ident(name), @@ -161,6 +212,33 @@ fn create_key_value(key: String, value: String) -> swc_ast::ObjectPatProp { }) } +fn create_await_import_expr(module_specifier: &str) -> Box { + use swc_ast::*; + Box::new(Expr::Await(AwaitExpr { + span: DUMMY_SP, + arg: Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: ExprOrSuper::Expr(Box::new(Expr::Ident(Ident { + span: DUMMY_SP, + sym: "import".into(), + optional: false, + }))), + args: vec![ExprOrSpread { + spread: None, + expr: Box::new(Expr::Lit(Lit::Str(Str { + span: DUMMY_SP, + has_escape: false, + kind: StrKind::Normal { + contains_quote: false, + }, + value: module_specifier.into(), + }))), + }], + type_args: None, + })), + })) +} + fn create_assignment(key: String) -> swc_ast::ObjectPatProp { swc_ast::ObjectPatProp::Assign(swc_ast::AssignPatProp { span: DUMMY_SP, @@ -264,6 +342,85 @@ mod test { ); } + #[test] + fn test_strip_exports_export_all() { + test_transform( + StripExportsFolder, + r#"export * from "./test.ts";"#, + r#"await import("./test.ts");"#, + ); + } + + #[test] + fn test_strip_exports_export_named() { + test_transform( + StripExportsFolder, + r#"export { test } from "./test.ts";"#, + r#"await import("./test.ts");"#, + ); + + test_transform(StripExportsFolder, r#"export { test };"#, ";"); + } + + #[test] + fn test_strip_exports_export_default_expr() { + test_transform(StripExportsFolder, "export default 5;", "5;"); + } + + #[test] + fn test_strip_exports_export_default_decl_name() { + test_transform( + StripExportsFolder, + "export default class Test {}", + "class Test {\n}", + ); + + test_transform( + StripExportsFolder, + "export default function test() {}", + "function test() {\n}", + ); + } + + #[test] + fn test_strip_exports_export_default_decl_no_name() { + test_transform(StripExportsFolder, "export default class {}", ";"); + + test_transform(StripExportsFolder, "export default function() {}", ";"); + } + + #[test] + fn test_strip_exports_export_named_decls() { + test_transform( + StripExportsFolder, + "export class Test {}", + "class Test {\n}", + ); + + test_transform( + StripExportsFolder, + "export function test() {}", + "function test() {\n}", + ); + + test_transform(StripExportsFolder, "export enum Test {}", "enum Test {\n}"); + + test_transform( + StripExportsFolder, + "export namespace Test {}", + "module Test {\n}", + ); + } + + #[test] + fn test_strip_exports_not_in_namespace() { + test_transform( + StripExportsFolder, + "namespace Test { export class Test {} }", + "module Test {\n export class Test {\n }\n}", + ); + } + fn test_transform( mut transform: impl Fold, src: &str, diff --git a/cli/tests/integration/repl_tests.rs b/cli/tests/integration/repl_tests.rs index 955c27069a..b96b398ae4 100644 --- a/cli/tests/integration/repl_tests.rs +++ b/cli/tests/integration/repl_tests.rs @@ -422,6 +422,19 @@ fn import_declarations() { assert!(out.contains("hello!\n")); } +#[test] +fn exports_stripped() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["export default 5;", "export class Test {}"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert!(out.contains("5\n")); + assert!(err.is_empty()); +} + #[test] fn eval_unterminated() { let (out, err) = util::run_and_collect_output(