From 8c5c7b55b74b8ab147ef6a791ba4ab6145454f72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Tue, 12 Feb 2019 11:23:19 +0900 Subject: [PATCH] Amd & Fix for exports (#164) swc_ecma_transforms: - correctly tracks exported names. - Implement amd Fix #162 --- ecmascript/transforms/src/modules/amd/mod.rs | 705 +++++++++++++ .../transforms/src/modules/amd/tests.rs | 988 ++++++++++++++++++ .../transforms/src/modules/common_js/mod.rs | 4 +- .../transforms/src/modules/common_js/tests.rs | 27 + ecmascript/transforms/src/modules/mod.rs | 1 + .../transforms/src/modules/umd/config.rs | 2 +- ecmascript/transforms/src/modules/umd/mod.rs | 10 +- ecmascript/transforms/src/modules/util.rs | 23 +- 8 files changed, 1746 insertions(+), 14 deletions(-) create mode 100644 ecmascript/transforms/src/modules/amd/mod.rs create mode 100644 ecmascript/transforms/src/modules/amd/tests.rs diff --git a/ecmascript/transforms/src/modules/amd/mod.rs b/ecmascript/transforms/src/modules/amd/mod.rs new file mode 100644 index 00000000000..84fe86b5b7c --- /dev/null +++ b/ecmascript/transforms/src/modules/amd/mod.rs @@ -0,0 +1,705 @@ +use super::util::{ + define_es_module, define_property, initialize_to_undefined, local_name_for_src, + make_descriptor, use_strict, Exports, Scope, VarCollector, +}; +use crate::{ + pass::Pass, + util::{prepend_stmts, DestructuringFinder, ExprFactory, State}, +}; +use ast::*; +use fxhash::FxHashSet; +use serde::{Deserialize, Serialize}; +use std::{collections::hash_map::Entry, iter}; +use swc_common::{Fold, FoldWith, Mark, VisitWith, DUMMY_SP}; + +#[cfg(test)] +mod tests; + +pub fn amd(config: Config) -> impl Pass + Clone { + Amd { + config, + scope: Default::default(), + exports: Default::default(), + } +} + +#[derive(Clone)] +struct Amd { + config: Config, + scope: State, + exports: State, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct Config { + pub module_id: Option, +} + +impl Fold for Amd { + fn fold(&mut self, module: Module) -> Module { + let items = module.body; + + // Inserted after initializing exported names to undefined. + let mut extra_stmts = vec![]; + let mut stmts = Vec::with_capacity(items.len() + 2); + stmts.push(use_strict()); + + let mut exports = vec![]; + let mut initialized = FxHashSet::default(); + let mut export_alls = vec![]; + let mut emitted_esmodule = false; + let exports_ident = self.exports.value.0.clone(); + + // Process items + for item in items { + let decl = match item { + ModuleItem::Stmt(stmt) => { + extra_stmts.push(stmt.fold_with(self)); + continue; + } + ModuleItem::ModuleDecl(decl) => decl, + }; + + match decl { + ModuleDecl::Import(import) => self.scope.insert_import(import), + + ModuleDecl::ExportAll(..) + | ModuleDecl::ExportDecl(..) + | ModuleDecl::ExportDefaultDecl(..) + | ModuleDecl::ExportDefaultExpr(..) + | ModuleDecl::ExportNamed(..) => { + if !emitted_esmodule { + emitted_esmodule = true; + stmts.push(define_es_module(exports_ident.clone())); + } + + macro_rules! init_export { + ("default") => {{ + init_export!(js_word!("default")) + }}; + ($name:expr) => {{ + exports.push($name.clone()); + initialized.insert($name.clone()); + }}; + } + match decl { + // Function declaration cannot throw an error. + ModuleDecl::ExportDefaultDecl(ExportDefaultDecl::Fn(..)) => { + // initialized.insert(js_word!("default")); + } + + ModuleDecl::ExportDefaultDecl(ExportDefaultDecl::TsInterfaceDecl(..)) => {} + + ModuleDecl::ExportAll(ref export) => { + self.scope + .value + .import_types + .entry(export.src.value.clone()) + .and_modify(|v| *v = true); + } + + ModuleDecl::ExportDefaultDecl(..) | ModuleDecl::ExportDefaultExpr(..) => { + // TODO: Optimization (when expr cannot throw, `exports.default = + // void 0` is not required) + init_export!("default") + } + _ => {} + } + + match decl { + ModuleDecl::ExportAll(export) => export_alls.push(export), + ModuleDecl::ExportDecl(decl @ Decl::Class(..)) + | ModuleDecl::ExportDecl(decl @ Decl::Fn(..)) => { + let (ident, is_class) = match decl { + Decl::Class(ref c) => (c.ident.clone(), true), + Decl::Fn(ref f) => (f.ident.clone(), false), + _ => unreachable!(), + }; + + // + extra_stmts.push(Stmt::Decl(decl.fold_with(self))); + + let append_to: &mut Vec<_> = if is_class { + &mut extra_stmts + } else { + // Function declaration cannot throw + &mut stmts + }; + + append_to.push(Stmt::Expr(box Expr::Assign(AssignExpr { + span: DUMMY_SP, + left: PatOrExpr::Expr( + box exports_ident.clone().member(ident.clone()), + ), + op: op!("="), + right: box ident.into(), + }))); + } + ModuleDecl::ExportDecl(Decl::Var(var)) => { + extra_stmts.push(Stmt::Decl(Decl::Var(var.clone().fold_with(self)))); + + var.decls.visit_with(&mut VarCollector { + to: &mut self.scope.value.declared_vars, + }); + + let mut found = vec![]; + for decl in var.decls { + let mut v = DestructuringFinder { found: &mut found }; + decl.visit_with(&mut v); + + for ident in found.drain(..).map(|v| Ident::new(v.0, v.1)) { + self.scope + .exported_vars + .entry((ident.sym.clone(), ident.span.ctxt())) + .or_default() + .push((ident.sym.clone(), ident.span.ctxt())); + init_export!(ident.sym); + + extra_stmts.push(Stmt::Expr(box Expr::Assign(AssignExpr { + span: DUMMY_SP, + left: PatOrExpr::Expr( + box exports_ident.clone().member(ident.clone()), + ), + op: op!("="), + right: box ident.into(), + }))); + } + } + } + ModuleDecl::ExportDefaultDecl(decl) => match decl { + ExportDefaultDecl::Class(ClassExpr { ident, class }) => { + let ident = ident.unwrap_or_else(|| private_ident!("_default")); + + extra_stmts.push(Stmt::Decl(Decl::Class(ClassDecl { + ident: ident.clone(), + class, + declare: false, + }))); + + extra_stmts.push(Stmt::Expr(box Expr::Assign(AssignExpr { + span: DUMMY_SP, + left: PatOrExpr::Expr( + box exports_ident.clone().member(quote_ident!("default")), + ), + op: op!("="), + right: box ident.into(), + }))); + } + ExportDefaultDecl::Fn(FnExpr { ident, function }) => { + let ident = ident.unwrap_or_else(|| private_ident!("_default")); + + extra_stmts.push(Stmt::Expr(box Expr::Assign(AssignExpr { + span: DUMMY_SP, + left: PatOrExpr::Expr( + box exports_ident.clone().member(quote_ident!("default")), + ), + op: op!("="), + right: box ident.clone().into(), + }))); + + extra_stmts.push(Stmt::Decl(Decl::Fn( + FnDecl { + ident, + function, + declare: false, + } + .fold_with(self), + ))); + } + _ => {} + }, + + ModuleDecl::ExportDefaultExpr(expr) => { + let ident = private_ident!("_default"); + + // We use extra statements because of the initialzation + extra_stmts.push(Stmt::Decl(Decl::Var(VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Var, + decls: vec![VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(ident.clone()), + init: Some(expr.fold_with(self)), + definite: false, + }], + declare: false, + }))); + extra_stmts.push(Stmt::Expr(box Expr::Assign(AssignExpr { + span: DUMMY_SP, + left: PatOrExpr::Expr( + box exports_ident.clone().member(quote_ident!("default")), + ), + op: op!("="), + right: box ident.into(), + }))); + } + + // export { foo } from 'foo'; + ModuleDecl::ExportNamed(export) => { + let imported = export.src.clone().map(|src| { + self.scope + .import_to_export(&src, !export.specifiers.is_empty()) + }); + + stmts.reserve(export.specifiers.len()); + + for ExportSpecifier { orig, exported, .. } in export.specifiers { + let is_import_default = orig.sym == js_word!("default"); + + let key = (orig.sym.clone(), orig.span.ctxt()); + if self.scope.value.declared_vars.contains(&key) { + self.scope + .exported_vars + .entry(key.clone()) + .or_default() + .push( + exported + .clone() + .map(|i| (i.sym.clone(), i.span.ctxt())) + .unwrap_or_else(|| { + (orig.sym.clone(), orig.span.ctxt()) + }), + ); + } + + if let Some(ref src) = export.src { + if is_import_default { + self.scope + .import_types + .entry(src.value.clone()) + .or_insert(false); + } + } + + let value = match imported { + Some(ref imported) => { + box imported.clone().unwrap().member(orig.clone()) + } + None => box Expr::Ident(orig.clone()).fold_with(self), + }; + + // True if we are exporting our own stuff. + let is_value_ident = match *value { + Expr::Ident(..) => true, + _ => false, + }; + + if is_value_ident { + let exported_symbol = exported + .as_ref() + .map(|e| e.sym.clone()) + .unwrap_or_else(|| orig.sym.clone()); + init_export!(exported_symbol); + + extra_stmts.push(Stmt::Expr(box Expr::Assign(AssignExpr { + span: DUMMY_SP, + left: PatOrExpr::Expr( + box exports_ident + .clone() + .member(exported.unwrap_or(orig)), + ), + op: op!("="), + right: value, + }))); + } else { + stmts.push(Stmt::Expr(box define_property(vec![ + exports_ident.clone().as_arg(), + { + // export { foo } + // -> 'foo' + + // export { foo as bar } + // -> 'bar' + let i = exported.unwrap_or_else(|| orig); + Lit::Str(quote_str!(i.span, i.sym)).as_arg() + }, + make_descriptor(value).as_arg(), + ]))); + } + } + } + + _ => {} + } + } + + ModuleDecl::TsImportEqualsDecl(..) + | ModuleDecl::TsExportAssignment(..) + | ModuleDecl::TsNamespaceExportDecl(..) => {} + } + } + + // ==================== + // Handle imports + // ==================== + + // Prepended to statements. + let mut import_stmts = vec![]; + let mut define_deps_arg = ArrayLit { + span: DUMMY_SP, + elems: vec![], + }; + + let mut factory_params = Vec::with_capacity(self.scope.imports.len() + 1); + if emitted_esmodule { + define_deps_arg + .elems + .push(Some(Lit::Str(quote_str!("exports")).as_arg())); + factory_params.push(Pat::Ident(exports_ident.clone())); + } + + // Used only if export * exists + let exported_names = { + if !export_alls.is_empty() && !exports.is_empty() { + let exported_names = private_ident!("_exportNames"); + stmts.push(Stmt::Decl(Decl::Var(VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Var, + decls: vec![VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(exported_names.clone()), + init: Some(box Expr::Object(ObjectLit { + span: DUMMY_SP, + props: exports + .into_iter() + .filter_map(|export| { + if export == js_word!("default") { + return None; + } + + Some(PropOrSpread::Prop(box Prop::KeyValue(KeyValueProp { + key: PropName::Ident(Ident::new(export, DUMMY_SP)), + value: box Expr::Lit(Lit::Bool(Bool { + span: DUMMY_SP, + value: true, + })), + }))) + }) + .collect(), + })), + definite: false, + }], + declare: false, + }))); + + Some(exported_names) + } else { + None + } + }; + + for export in export_alls { + stmts.push(self.scope.handle_export_all( + exports_ident.clone(), + exported_names.clone(), + export, + )); + } + + if !initialized.is_empty() { + stmts.push(Stmt::Expr(initialize_to_undefined( + exports_ident.clone(), + initialized, + ))); + } + + for (src, import) in self.scope.value.imports.drain(..) { + let import = import.unwrap_or_else(|| { + ( + local_name_for_src(&src), + DUMMY_SP.apply_mark(Mark::fresh(Mark::root())), + ) + }); + let ident = Ident::new(import.0.clone(), import.1); + + define_deps_arg + .elems + .push(Some(Lit::Str(quote_str!(src.clone())).as_arg())); + factory_params.push(Pat::Ident(ident.clone())); + + { + // handle interop + let ty = self.scope.value.import_types.get(&src); + + match ty { + Some(&wildcard) => { + let right = box Expr::Call(CallExpr { + span: DUMMY_SP, + callee: if wildcard { + helper!(interop_require_wildcard); + quote_ident!("_interopRequireWildcard").as_callee() + } else { + helper!(interop_require_default); + quote_ident!("_interopRequireDefault").as_callee() + }, + args: vec![ident.clone().as_arg()], + type_args: Default::default(), + }); + + import_stmts.push(Stmt::Expr(box Expr::Assign(AssignExpr { + span: DUMMY_SP, + left: PatOrExpr::Pat(box Pat::Ident(ident.clone())), + op: op!("="), + right, + }))); + } + _ => {} + }; + } + } + + prepend_stmts(&mut stmts, import_stmts.into_iter()); + stmts.append(&mut extra_stmts); + + // ==================== + // Emit + // ==================== + + Module { + span: module.span, + body: vec![ModuleItem::Stmt(Stmt::Expr(box Expr::Call(CallExpr { + span: DUMMY_SP, + callee: quote_ident!("define").as_callee(), + args: self + .config + .module_id + .clone() + .map(|s| quote_str!(s).as_arg()) + .into_iter() + .chain(iter::once(define_deps_arg.as_arg())) + .chain(iter::once( + FnExpr { + ident: None, + function: Function { + span: DUMMY_SP, + is_async: false, + is_generator: false, + decorators: Default::default(), + params: factory_params, + body: Some(BlockStmt { + span: DUMMY_SP, + stmts, + }), + type_params: Default::default(), + return_type: Default::default(), + }, + } + .as_arg(), + )) + .collect(), + type_args: Default::default(), + })))], + } + } +} + +impl Fold for Amd { + fn fold(&mut self, expr: Expr) -> Expr { + let exports_ident = self.exports.value.0.clone(); + + macro_rules! entry { + ($i:expr) => { + self.scope + .value + .exported_vars + .entry(($i.sym.clone(), $i.span.ctxt())) + }; + } + + macro_rules! chain_assign { + ($entry:expr, $e:expr) => {{ + let mut e = $e; + for i in $entry.get() { + e = box Expr::Assign(AssignExpr { + span: DUMMY_SP, + left: PatOrExpr::Expr( + box exports_ident + .clone() + .member(Ident::new(i.0.clone(), DUMMY_SP.with_ctxt(i.1))), + ), + op: op!("="), + right: e, + }); + } + e + }}; + } + + match expr { + Expr::Ident(i) => { + let v = self.scope.value.idents.get(&(i.sym.clone(), i.span.ctxt())); + match v { + None => return Expr::Ident(i), + Some((src, prop)) => { + let (ident, span) = self + .scope + .value + .imports + .get(src) + .as_ref() + .unwrap() + .as_ref() + .unwrap(); + + let obj = { + let ident = Ident::new(ident.clone(), *span); + + Expr::Ident(ident) + }; + + if *prop == js_word!("") { + // import * as foo from 'foo'; + obj + } else { + obj.member(Ident::new(prop.clone(), DUMMY_SP)) + } + } + } + } + Expr::Member(e) => { + if e.computed { + Expr::Member(MemberExpr { + obj: e.obj.fold_with(self), + prop: e.prop.fold_with(self), + ..e + }) + } else { + Expr::Member(MemberExpr { + obj: e.obj.fold_with(self), + ..e + }) + } + } + + Expr::Update(UpdateExpr { + span, + arg: box Expr::Ident(arg), + op, + prefix, + }) => { + let entry = entry!(arg); + + match entry { + Entry::Occupied(entry) => { + let e = chain_assign!( + entry, + box Expr::Assign(AssignExpr { + span: DUMMY_SP, + left: PatOrExpr::Pat(box Pat::Ident(arg.clone())), + op: op!("="), + right: box Expr::Bin(BinExpr { + span: DUMMY_SP, + left: box Expr::Unary(UnaryExpr { + span: DUMMY_SP, + op: op!(unary, "+"), + arg: box Expr::Ident(arg) + }), + op: match op { + op!("++") => op!(bin, "+"), + op!("--") => op!(bin, "-"), + }, + right: box Expr::Lit(Lit::Num(Number { + span: DUMMY_SP, + value: 1.0, + })), + }), + }) + ); + + *e + } + _ => { + return Expr::Update(UpdateExpr { + span, + arg: box Expr::Ident(arg), + op, + prefix, + }); + } + } + } + + Expr::Assign(expr) => { + // + + match expr.left { + PatOrExpr::Pat(box Pat::Ident(ref i)) => { + let entry = entry!(i); + + match entry { + Entry::Occupied(entry) => { + let e = chain_assign!(entry, box Expr::Assign(expr)); + + *e + } + _ => { + return Expr::Assign(AssignExpr { + left: expr.left, + ..expr + }); + } + } + } + _ => { + let mut found = vec![]; + let mut v = DestructuringFinder { found: &mut found }; + expr.left.visit_with(&mut v); + if v.found.is_empty() { + return Expr::Assign(AssignExpr { + left: expr.left, + ..expr + }); + } + + let mut exprs = iter::once(box Expr::Assign(expr)) + .chain( + found + .into_iter() + .map(|var| Ident::new(var.0, var.1)) + .filter_map(|i| { + let entry = match entry!(i) { + Entry::Occupied(entry) => entry, + _ => { + return None; + } + }; + let e = chain_assign!(entry, box Expr::Ident(i)); + + // exports.name = x + Some(e) + }), + ) + .collect::>(); + if exprs.len() == 1 { + return *exprs.pop().unwrap(); + } + + Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs, + }) + } + } + } + _ => expr.fold_children(self), + } + } +} + +impl Fold for Amd { + /// + /// - collects all declared variables for let and var. + fn fold(&mut self, var: VarDecl) -> VarDecl { + if var.kind != VarDeclKind::Const { + var.decls.visit_with(&mut VarCollector { + to: &mut self.scope.value.declared_vars, + }); + } + + VarDecl { + decls: var.decls.fold_with(self), + ..var + } + } +} diff --git a/ecmascript/transforms/src/modules/amd/tests.rs b/ecmascript/transforms/src/modules/amd/tests.rs new file mode 100644 index 00000000000..427f8b8dda0 --- /dev/null +++ b/ecmascript/transforms/src/modules/amd/tests.rs @@ -0,0 +1,988 @@ +use super::{amd, Config}; +use ast::Module; +use swc_common::Fold; +use swc_ecma_parser::Syntax; + +fn syntax() -> Syntax { + Default::default() +} + +fn tr(config: Config) -> impl Fold { + amd(config) +} + +test!( + syntax(), + |_| tr(Config { + module_id: Some("moduleId".into()), + ..Default::default() + }), + custom_named_define, + r#" +import {foo} from 'src'; +export {foo}; + "#, + r#"define('moduleId', ['exports', 'src'], function(_exports, _src) { + 'use strict'; + Object.defineProperty(_exports, '__esModule', { + value: true + }); + Object.defineProperty(_exports, 'foo', { + enumerable: true, + get: function() { + return _src.foo; + } + }); +}); +"# +); + +// export_default_4 +test!( + syntax(), + |_| tr(Default::default()), + export_default_4, + r#" +export default foo; + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.default = void 0; + var _default = foo; + _exports.default = _default; +}); + +"# +); + +// export_from_2 +test!( + syntax(), + |_| tr(Default::default()), + export_from_2, + r#" +export {foo} from "foo"; + +"#, + r#" +define(["exports", "foo"], function (_exports, _foo) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + Object.defineProperty(_exports, "foo", { + enumerable: true, + get: function () { + return _foo.foo; + } + }); +}); + +"# +); + +// export_named_2 +test!( + syntax(), + |_| tr(Default::default()), + export_named_2, + r#" +var foo, bar; +export {foo, bar}; + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.bar = _exports.foo = void 0; + var foo, bar; + _exports.foo = foo; + _exports.bar = bar; +}); + +"# +); + +// imports_default +test!( + syntax(), + |_| tr(Default::default()), + imports_default, + r#" +import foo from "foo"; +import {default as foo2} from "foo"; + +foo; +foo2; + +"#, + r#" +define(["foo"], function (_foo) { + "use strict"; + + _foo = babelHelpers.interopRequireDefault(_foo); + _foo.default; + _foo.default; +}); + +"# +); + +// imports_mixing +test!( + syntax(), + |_| tr(Default::default()), + imports_mixing, + r#" +import foo, {baz as xyz} from "foo"; + +foo; +xyz; + +"#, + r#" +define(["foo"], function (_foo) { + "use strict"; + + _foo = babelHelpers.interopRequireWildcard(_foo); + _foo.default; + _foo.baz; +}); + +"# +); + +// export_default_9 +test!( + syntax(), + |_| tr(Default::default()), + export_default_9, + r#" +var foo; +export { foo as default }; + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.default = void 0; + var foo; + _exports.default = foo; +}); + +"# +); + +// noInterop_export_from + +// amd + +// export_default +test!( + syntax(), + |_| tr(Default::default()), + export_default, + r#" +export default 42; + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.default = void 0; + var _default = 42; + _exports.default = _default; +}); + +"# +); + +// export_default_2 +test!( + syntax(), + |_| tr(Default::default()), + export_default_2, + r#" +export default {}; + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.default = void 0; + var _default = {}; + _exports.default = _default; +}); + +"# +); + +// export_from_4 +test!( + syntax(), + |_| tr(Default::default()), + export_from_4, + r#" +export {foo as bar} from "foo"; + +"#, + r#" +define(["exports", "foo"], function (_exports, _foo) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + Object.defineProperty(_exports, "bar", { + enumerable: true, + get: function () { + return _foo.foo; + } + }); +}); + +"# +); + +// export_named +test!( + syntax(), + |_| tr(Default::default()), + export_named, + r#" +var foo; +export {foo}; + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.foo = void 0; + var foo; + _exports.foo = foo; +}); + +"# +); + +// noInterop_import_default_only + +// export_from +test!( + syntax(), + |_| tr(Default::default()), + export_from, + r#" +export * from "foo"; + +"#, + r#" +define(["exports", "foo"], function (_exports, _foo) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + Object.keys(_foo).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + Object.defineProperty(_exports, key, { + enumerable: true, + get: function () { + return _foo[key]; + } + }); + }); +}); + +"# +); + +// export_default_7 +test!( + syntax(), + |_| tr(Default::default()), + export_default_7, + r#" +export default function foo () {} + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.default = foo; + + function foo() {} +}); + +"# +); + +// export_named_4 +test!( + syntax(), + |_| tr(Default::default()), + export_named_4, + r#" +var foo; +export {foo as default}; + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.default = void 0; + var foo; + _exports.default = foo; +}); + +"# +); + +// imports_glob +test!( + syntax(), + |_| tr(Default::default()), + imports_glob, + r#" +import * as foo from "foo"; + +foo; + +"#, + r#" +define(["foo"], function (foo) { + "use strict"; + + foo = babelHelpers.interopRequireWildcard(foo); + foo; +}); + +"# +); + +// remap +test!( + syntax(), + |_| tr(Default::default()), + remap, + r#" +export var test = 2; +test = 5; +test++; + +(function () { + var test = 2; + test = 3; + test++; +})(); + +var a = 2; +export { a }; +a = 3; + +var b = 2; +export { b as c }; +b = 3; + +var d = 3; +export { d as e, d as f }; +d = 4; + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.f = _exports.e = _exports.c = _exports.a = _exports.test = void 0; + var test = 2; + _exports.test = test; + _exports.test = test = 5; + _exports.test = test = test + 1; + + (function () { + var test = 2; + test = 3; + test++; + })(); + + var a = 2; + _exports.a = a; + _exports.a = a = 3; + var b = 2; + _exports.c = b; + _exports.c = b = 3; + var d = 3; + _exports.f = _exports.e = d; + _exports.f = _exports.e = d = 4; +}); + +"# +); + +// regression_4192 + +// imports +test!( + syntax(), + |_| tr(Default::default()), + imports, + r#" +import "foo"; +import "foo-bar"; +import "./directory/foo-bar"; + +"#, + r#" +define(["foo", "foo-bar", "./directory/foo-bar"], function (_foo, _fooBar, _fooBar2) { + "use strict"; +}); + +"# +); + +// export_from_3 +test!( + syntax(), + |_| tr(Default::default()), + export_from_3, + r#" +export {foo, bar} from "foo"; + +"#, + r#" +define(["exports", "foo"], function (_exports, _foo) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + Object.defineProperty(_exports, "foo", { + enumerable: true, + get: function () { + return _foo.foo; + } + }); + Object.defineProperty(_exports, "bar", { + enumerable: true, + get: function () { + return _foo.bar; + } + }); +}); + +"# +); + +// export_default_5 +test!( + syntax(), + |_| tr(Default::default()), + export_default_5, + r#" +export default function () {} + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.default = _default; + + function _default() {} +}); + +"# +); + +// export_default_10 +test!( + syntax(), + |_| tr(Default::default()), + export_default_10, + r#" +export default (function(){return "foo"})(); + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.default = void 0; + + var _default = function () { + return "foo"; + }(); + + _exports.default = _default; +}); + +"# +); + +// export_named_3 +test!( + syntax(), + |_| tr(Default::default()), + export_named_3, + r#" +var foo; +export {foo as bar}; + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.bar = void 0; + var foo; + _exports.bar = foo; +}); + +"# +); + +// overview +test!( + syntax(), + |_| tr(Default::default()), + overview, + r#" +import "foo"; +import "foo-bar"; +import "./directory/foo-bar"; +import foo from "foo"; +import * as foo2 from "foo"; +import {bar} from "foo"; +import {foo as bar2} from "foo"; + +var test; +export {test}; +export var test2 = 5; + +export default test; + +foo; +foo2; +bar; +bar2; + +"#, + r#" +define(["exports", "foo", "foo-bar", "./directory/foo-bar"], +function (_exports, foo2, _fooBar, _fooBar2) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.default = _exports.test2 = _exports.test = void 0; + foo2 = babelHelpers.interopRequireWildcard(foo2); + var test; + _exports.test = test; + var test2 = 5; + _exports.test2 = test2; + var _default = test; + _exports.default = _default; + foo2.default; + foo2; + foo2.bar; + foo2.foo; +}); + +"# +); + +// export_from_6 +test!( + syntax(), + |_| tr(Default::default()), + export_from_6, + r#" +export {foo as default, bar} from "foo"; + +"#, + r#" +define(["exports", "foo"], function (_exports, _foo) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + Object.defineProperty(_exports, "default", { + enumerable: true, + get: function () { + return _foo.foo; + } + }); + Object.defineProperty(_exports, "bar", { + enumerable: true, + get: function () { + return _foo.bar; + } + }); +}); + +"# +); + +// hoist_function_exports +test!( + syntax(), + |_| tr(Default::default()), + hoist_function_exports, + r#" +import { isEven } from "./evens"; + +export function nextOdd(n) { + return isEven(n) ? n + 1 : n + 2; +} + +export var isOdd = (function (isEven) { + return function (n) { + return !isEven(n); + }; +})(isEven); + +"#, + r#" +define(["exports", "./evens"], function (_exports, _evens) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.nextOdd = nextOdd; + _exports.isOdd = void 0; + + function nextOdd(n) { + return _evens.isEven(n) ? n + 1 : n + 2; + } + + var isOdd = function (isEven) { + return function (n) { + return !isEven(n); + }; + }(_evens.isEven); + + _exports.isOdd = isOdd; +}); + +"# +); + +// export_default_8 +test!( + syntax(), + |_| tr(Default::default()), + export_default_8, + r#" +export default class Foo {} + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.default = void 0; + + class Foo {} + + _exports.default = Foo; +}); + +"# +); + +// export_from_5 +test!( + syntax(), + |_| tr(Default::default()), + export_from_5, + r#" +export {foo as default} from "foo"; + +"#, + r#" +define(["exports", "foo"], function (_exports, _foo) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + Object.defineProperty(_exports, "default", { + enumerable: true, + get: function () { + return _foo.foo; + } + }); +}); + +"# +); + +// export_default_3 +test!( + syntax(), + |_| tr(Default::default()), + export_default_3, + r#" +export default []; + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.default = void 0; + var _default = []; + _exports.default = _default; +}); + +"# +); + +// import_order +test!( + syntax(), + |_| tr(Default::default()), + import_order, + r#" +import './foo'; +import bar from './bar'; +import './derp'; +import { qux } from './qux'; + +"#, + r#" +define(["./foo", "./bar", "./derp", "./qux"], function (_foo, _bar, _derp, _qux) { + "use strict"; + + _bar = babelHelpers.interopRequireDefault(_bar); +}); + +"# +); + +// export_specifier_default +test!( + syntax(), + |_| tr(Default::default()), + export_specifier_default, + r#" +var a = 1; +export { a as default }; + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.default = void 0; + var a = 1; + _exports.default = a; +}); + +"# +); + +// exports_variable +test!( + syntax(), + |_| tr(Default::default()), + exports_variable, + r#" +export var foo = 1; +export var foo2 = 1, bar = 2; +export var foo3 = function () {}; +export var foo4; +export let foo5 = 2; +export let foo6; +export const foo7 = 3; +export function foo8 () {} +export class foo9 {} + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.foo8 = foo8; + _exports.foo3 = _exports.foo4 = _exports.foo2 = _exports.foo7 = _exports.bar = + _exports.foo = _exports.foo5 = _exports.foo6 = void 0; + var foo = 1; + _exports.foo = foo; + var foo2 = 1, + bar = 2; + _exports.foo2 = foo2; + _exports.bar = bar; + + var foo3 = function () {}; + + _exports.foo3 = foo3; + var foo4; + _exports.foo4 = foo4; + let foo5 = 2; + _exports.foo5 = foo5; + let foo6; + _exports.foo6 = foo6; + const foo7 = 3; + _exports.foo7 = foo7; + + function foo8() {} + + class foo9 {} + + _exports.foo9 = foo9; +}); + +"# +); + +// imports_named +test!( + syntax(), + |_| tr(Default::default()), + imports_named, + r#" +import {bar} from "foo"; +import {bar2, baz} from "foo"; +import {bar as baz2} from "foo"; +import {bar as baz3, xyz} from "foo"; + +bar; +bar2; +baz; +baz2; +baz3; +xyz; + +"#, + r#" +define(["foo"], function (_foo) { + "use strict"; + + _foo.bar; + _foo.bar2; + _foo.baz; + _foo.bar; + _foo.bar; + _foo.xyz; +}); + +"# +); + +// export_default_6 +test!( + syntax(), + |_| tr(Default::default()), + export_default_6, + r#" +export default class {} + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.default = void 0; + + class _default {} + + _exports.default = _default; +}); + +"# +); + +// get_module_name_option + +// module_name + +// export_named_5 +test!( + syntax(), + |_| tr(Default::default()), + export_named_5, + r#" +var foo, bar; +export {foo as default, bar}; + +"#, + r#" +define(["exports"], function (_exports) { + "use strict"; + + Object.defineProperty(_exports, "__esModule", { + value: true + }); + _exports.default = _exports.bar = void 0; + var foo, bar; + _exports.default = foo; + _exports.bar = bar; +}); + +"# +); diff --git a/ecmascript/transforms/src/modules/common_js/mod.rs b/ecmascript/transforms/src/modules/common_js/mod.rs index 1ee10deda2e..4ae102d3d9f 100644 --- a/ecmascript/transforms/src/modules/common_js/mod.rs +++ b/ecmascript/transforms/src/modules/common_js/mod.rs @@ -16,7 +16,7 @@ use swc_common::{Fold, FoldWith, VisitWith, DUMMY_SP}; #[cfg(test)] mod tests; -#[derive(Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct Config { #[serde(default)] @@ -44,7 +44,7 @@ const fn default_strict_mode() -> bool { true } -#[derive(Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged, deny_unknown_fields, rename_all = "camelCase")] pub enum Lazy { Bool(bool), diff --git a/ecmascript/transforms/src/modules/common_js/tests.rs b/ecmascript/transforms/src/modules/common_js/tests.rs index 276717906ab..984aee8ebf9 100644 --- a/ecmascript/transforms/src/modules/common_js/tests.rs +++ b/ecmascript/transforms/src/modules/common_js/tests.rs @@ -34,6 +34,33 @@ var foo = 2; exports.foo = foo; exports.foo = foo = 3; +"# +); +test!( + syntax(), + |_| tr(Config { + ..Default::default() + }), + custom_02, + r#" +export const good = { + a(bad1) { + (...bad2) => { }; + } +};"#, + r#" +'use strict'; +Object.defineProperty(exports, '__esModule', { + value: true +}); +exports.good = void 0; +const good = { + a (bad1) { + (...bad2)=>{}; + } +}; +exports.good = good; + "# ); diff --git a/ecmascript/transforms/src/modules/mod.rs b/ecmascript/transforms/src/modules/mod.rs index 16dcd8fd623..50eada84dc4 100644 --- a/ecmascript/transforms/src/modules/mod.rs +++ b/ecmascript/transforms/src/modules/mod.rs @@ -1,3 +1,4 @@ +pub mod amd; pub mod common_js; pub mod umd; mod util; diff --git a/ecmascript/transforms/src/modules/umd/config.rs b/ecmascript/transforms/src/modules/umd/config.rs index 8cfd2cb834f..1b3137939af 100644 --- a/ecmascript/transforms/src/modules/umd/config.rs +++ b/ecmascript/transforms/src/modules/umd/config.rs @@ -10,7 +10,7 @@ use swc_common::{ }; use swc_ecma_parser::{Parser, Session, SourceFileInput, Syntax}; -#[derive(Default, Clone, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct Config { #[serde(default)] diff --git a/ecmascript/transforms/src/modules/umd/mod.rs b/ecmascript/transforms/src/modules/umd/mod.rs index 4a4e416e1b5..f9c9436e29d 100644 --- a/ecmascript/transforms/src/modules/umd/mod.rs +++ b/ecmascript/transforms/src/modules/umd/mod.rs @@ -2,7 +2,7 @@ use self::config::BuiltConfig; pub use self::config::Config; use super::util::{ define_es_module, define_property, initialize_to_undefined, local_name_for_src, - make_descriptor, make_require_call, use_strict, Scope, VarCollector, + make_descriptor, make_require_call, use_strict, Exports, Scope, VarCollector, }; use crate::{ pass::Pass, @@ -35,14 +35,6 @@ struct Umd { exports: State, } -struct Exports(Ident); - -impl Default for Exports { - fn default() -> Self { - Exports(private_ident!("_exports")) - } -} - impl Fold for Umd { fn fold(&mut self, module: Module) -> Module { let filename = self.cm.span_to_filename(module.span); diff --git a/ecmascript/transforms/src/modules/util.rs b/ecmascript/transforms/src/modules/util.rs index 284ef47d1c8..412b9c281e2 100644 --- a/ecmascript/transforms/src/modules/util.rs +++ b/ecmascript/transforms/src/modules/util.rs @@ -423,6 +423,25 @@ impl<'a> Visit for VarCollector<'a> { self.to.push((i.sym.clone(), i.span.ctxt())) } } -impl<'a> Visit for VarCollector<'a> { - fn visit(&mut self, _: &Expr) {} + +macro_rules! var_noop { + ($T:path) => { + impl<'a> Visit<$T> for VarCollector<'a> { + fn visit(&mut self, _: &$T) {} + } + }; +} + +var_noop!(Expr); +var_noop!(ArrowExpr); +var_noop!(Function); +var_noop!(Constructor); + +/// Private `_exports` ident. +pub(super) struct Exports(pub Ident); + +impl Default for Exports { + fn default() -> Self { + Exports(private_ident!("_exports")) + } }