summaryrefslogtreecommitdiff
path: root/cli/ast/transforms.rs
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2021-06-24 09:00:46 -0400
committerGitHub <noreply@github.com>2021-06-24 09:00:46 -0400
commit4b3845b998ae2f7cd6e02625b4cfc3613878ceb2 (patch)
tree8b56e3e89cdf8977b5b5ceaa27e571ad157ce3c0 /cli/ast/transforms.rs
parent8ed83cba7e2eeabb3af1fd65579dacd782f50644 (diff)
feat(repl): support import declarations in the REPL (#11086)
Diffstat (limited to 'cli/ast/transforms.rs')
-rw-r--r--cli/ast/transforms.rs307
1 files changed, 307 insertions, 0 deletions
diff --git a/cli/ast/transforms.rs b/cli/ast/transforms.rs
new file mode 100644
index 000000000..570a0bf5b
--- /dev/null
+++ b/cli/ast/transforms.rs
@@ -0,0 +1,307 @@
+use swc_common::DUMMY_SP;
+use swc_ecmascript::ast as swc_ast;
+use swc_ecmascript::visit::noop_fold_type;
+use swc_ecmascript::visit::Fold;
+
+/// Transforms import declarations to variable declarations
+/// with a dynamic import. This is used to provide import
+/// declaration support in the REPL.
+pub struct DownlevelImportsFolder;
+
+impl Fold for DownlevelImportsFolder {
+ 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::Import(import_decl)) => {
+ // Handle type only imports
+ if import_decl.type_only {
+ return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }));
+ }
+
+ // 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,
+ })),
+ }));
+
+ // Handle imports for the side effects
+ // ex. `import "module.ts"` -> `await import("module.ts");`
+ if import_decl.specifiers.is_empty() {
+ return ModuleItem::Stmt(Stmt::Expr(ExprStmt {
+ span: DUMMY_SP,
+ expr: initializer,
+ }));
+ }
+
+ // Collect the specifiers and create the variable statement
+ let named_import_props = import_decl
+ .specifiers
+ .iter()
+ .filter_map(|specifier| match specifier {
+ ImportSpecifier::Default(specifier) => Some(create_key_value(
+ "default".to_string(),
+ specifier.local.sym.to_string(),
+ )),
+ ImportSpecifier::Named(specifier) => {
+ Some(match specifier.imported.as_ref() {
+ Some(name) => create_key_value(
+ name.sym.to_string(),
+ specifier.local.sym.to_string(),
+ ),
+ None => create_assignment(specifier.local.sym.to_string()),
+ })
+ }
+ ImportSpecifier::Namespace(_) => None,
+ })
+ .collect::<Vec<_>>();
+ let namespace_import_name =
+ import_decl
+ .specifiers
+ .iter()
+ .find_map(|specifier| match specifier {
+ ImportSpecifier::Namespace(specifier) => {
+ Some(create_binding_ident(specifier.local.sym.to_string()))
+ }
+ _ => None,
+ });
+
+ ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
+ span: DUMMY_SP,
+ kind: VarDeclKind::Const,
+ declare: false,
+ decls: {
+ let mut decls = Vec::new();
+
+ if !named_import_props.is_empty() {
+ decls.push(VarDeclarator {
+ span: DUMMY_SP,
+ name: Pat::Object(ObjectPat {
+ span: DUMMY_SP,
+ optional: false,
+ props: named_import_props,
+ type_ann: None,
+ }),
+ definite: false,
+ init: Some(initializer.clone()),
+ });
+ }
+ if let Some(namespace_import) = namespace_import_name {
+ decls.push(VarDeclarator {
+ span: DUMMY_SP,
+ name: Pat::Ident(namespace_import),
+ definite: false,
+ init: Some(initializer),
+ });
+ }
+
+ decls
+ },
+ })))
+ }
+ _ => module_item,
+ }
+ }
+}
+
+fn create_binding_ident(name: String) -> swc_ast::BindingIdent {
+ swc_ast::BindingIdent {
+ id: create_ident(name),
+ type_ann: None,
+ }
+}
+
+fn create_ident(name: String) -> swc_ast::Ident {
+ swc_ast::Ident {
+ span: DUMMY_SP,
+ sym: name.into(),
+ optional: false,
+ }
+}
+
+fn create_key_value(key: String, value: String) -> swc_ast::ObjectPatProp {
+ swc_ast::ObjectPatProp::KeyValue(swc_ast::KeyValuePatProp {
+ key: swc_ast::PropName::Ident(swc_ast::Ident {
+ span: DUMMY_SP,
+ sym: key.into(),
+ optional: false,
+ }),
+ value: Box::new(swc_ast::Pat::Ident(swc_ast::BindingIdent {
+ id: swc_ast::Ident {
+ span: DUMMY_SP,
+ sym: value.into(),
+ optional: false,
+ },
+ type_ann: None,
+ })),
+ })
+}
+
+fn create_assignment(key: String) -> swc_ast::ObjectPatProp {
+ swc_ast::ObjectPatProp::Assign(swc_ast::AssignPatProp {
+ span: DUMMY_SP,
+ key: create_ident(key),
+ value: None,
+ })
+}
+
+#[cfg(test)]
+mod test {
+ use std::rc::Rc;
+ use swc_common::FileName;
+ use swc_common::SourceMap;
+ use swc_ecmascript::ast::Module;
+ use swc_ecmascript::codegen::text_writer::JsWriter;
+ use swc_ecmascript::codegen::Node;
+ use swc_ecmascript::parser::Parser;
+ use swc_ecmascript::parser::StringInput;
+ use swc_ecmascript::parser::Syntax;
+ use swc_ecmascript::parser::TsConfig;
+ use swc_ecmascript::visit::Fold;
+ use swc_ecmascript::visit::FoldWith;
+
+ use super::*;
+
+ #[test]
+ fn test_downlevel_imports_type_only() {
+ test_transform(
+ DownlevelImportsFolder,
+ r#"import type { test } from "./mod.ts";"#,
+ ";",
+ );
+ }
+
+ #[test]
+ fn test_downlevel_imports_specifier_only() {
+ test_transform(
+ DownlevelImportsFolder,
+ r#"import "./mod.ts";"#,
+ r#"await import("./mod.ts");"#,
+ );
+
+ test_transform(
+ DownlevelImportsFolder,
+ r#"import {} from "./mod.ts";"#,
+ r#"await import("./mod.ts");"#,
+ );
+ }
+
+ #[test]
+ fn test_downlevel_imports_default() {
+ test_transform(
+ DownlevelImportsFolder,
+ r#"import mod from "./mod.ts";"#,
+ r#"const { default: mod } = await import("./mod.ts");"#,
+ );
+ }
+
+ #[test]
+ fn test_downlevel_imports_named() {
+ test_transform(
+ DownlevelImportsFolder,
+ r#"import { A } from "./mod.ts";"#,
+ r#"const { A } = await import("./mod.ts");"#,
+ );
+
+ test_transform(
+ DownlevelImportsFolder,
+ r#"import { A, B, C } from "./mod.ts";"#,
+ r#"const { A , B , C } = await import("./mod.ts");"#,
+ );
+
+ test_transform(
+ DownlevelImportsFolder,
+ r#"import { A as LocalA, B, C as LocalC } from "./mod.ts";"#,
+ r#"const { A: LocalA , B , C: LocalC } = await import("./mod.ts");"#,
+ );
+ }
+
+ #[test]
+ fn test_downlevel_imports_namespace() {
+ test_transform(
+ DownlevelImportsFolder,
+ r#"import * as mod from "./mod.ts";"#,
+ r#"const mod = await import("./mod.ts");"#,
+ );
+ }
+
+ #[test]
+ fn test_downlevel_imports_mixed() {
+ test_transform(
+ DownlevelImportsFolder,
+ r#"import myDefault, { A, B as LocalB } from "./mod.ts";"#,
+ r#"const { default: myDefault , A , B: LocalB } = await import("./mod.ts");"#,
+ );
+
+ test_transform(
+ DownlevelImportsFolder,
+ r#"import myDefault, * as mod from "./mod.ts";"#,
+ r#"const { default: myDefault } = await import("./mod.ts"), mod = await import("./mod.ts");"#,
+ );
+ }
+
+ fn test_transform(
+ mut transform: impl Fold,
+ src: &str,
+ expected_output: &str,
+ ) {
+ let (source_map, module) = parse(src);
+ let output = print(source_map, module.fold_with(&mut transform));
+ assert_eq!(output, format!("{}\n", expected_output));
+ }
+
+ fn parse(src: &str) -> (Rc<SourceMap>, Module) {
+ let source_map = Rc::new(SourceMap::default());
+ let source_file = source_map.new_source_file(
+ FileName::Custom("file.ts".to_string()),
+ src.to_string(),
+ );
+ let input = StringInput::from(&*source_file);
+ let syntax = Syntax::Typescript(TsConfig {
+ ..Default::default()
+ });
+ let mut parser = Parser::new(syntax, input, None);
+ (source_map, parser.parse_module().unwrap())
+ }
+
+ fn print(source_map: Rc<SourceMap>, module: Module) -> String {
+ let mut buf = vec![];
+ {
+ let writer =
+ Box::new(JsWriter::new(source_map.clone(), "\n", &mut buf, None));
+ let config = swc_ecmascript::codegen::Config { minify: false };
+ let mut emitter = swc_ecmascript::codegen::Emitter {
+ cfg: config,
+ comments: None,
+ cm: source_map,
+ wr: writer,
+ };
+ module.emit_with(&mut emitter).unwrap();
+ }
+ String::from_utf8(buf).unwrap()
+ }
+}