1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-22 15:06:54 -05:00

feat(repl): support import declarations in the REPL (#11086)

This commit is contained in:
David Sherret 2021-06-24 09:00:46 -04:00 committed by GitHub
parent 8ed83cba7e
commit 4b3845b998
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 329 additions and 0 deletions

View file

@ -43,6 +43,8 @@ use swc_ecmascript::transforms::react;
use swc_ecmascript::transforms::typescript;
use swc_ecmascript::visit::FoldWith;
mod transforms;
static TARGET: JscTarget = JscTarget::Es2020;
#[derive(Debug, Clone, Eq, PartialEq)]
@ -167,6 +169,9 @@ pub struct EmitOptions {
pub jsx_fragment_factory: String,
/// Should JSX be transformed or preserved. Defaults to `true`.
pub transform_jsx: bool,
/// Should import declarations be transformed to variable declarations.
/// This should only be set to true for the REPL. Defaults to `false`.
pub repl_imports: bool,
}
impl Default for EmitOptions {
@ -179,6 +184,7 @@ impl Default for EmitOptions {
jsx_factory: "React.createElement".into(),
jsx_fragment_factory: "React.Fragment".into(),
transform_jsx: true,
repl_imports: false,
}
}
}
@ -201,6 +207,7 @@ impl From<config_file::TsConfig> for EmitOptions {
jsx_factory: options.jsx_factory,
jsx_fragment_factory: options.jsx_fragment_factory,
transform_jsx: options.jsx == "react",
repl_imports: false,
}
}
}
@ -306,10 +313,12 @@ impl ParsedModule {
);
let mut passes = chain!(
Optional::new(jsx_pass, options.transform_jsx),
Optional::new(transforms::DownlevelImportsFolder, options.repl_imports),
proposals::decorators::decorators(proposals::decorators::Config {
legacy: true,
emit_metadata: options.emit_metadata
}),
// DownlevelImportsFolder::new(), // todo: make this conditional
helpers::inject_helpers(),
typescript::strip::strip_with_config(strip_config_from_emit_options(
options

307
cli/ast/transforms.rs Normal file
View file

@ -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()
}
}

View file

@ -2369,6 +2369,18 @@ mod integration {
assert!(out.contains("hello!\n"));
}
#[test]
fn import_declarations() {
let (out, _) = util::run_and_collect_output(
true,
"repl",
Some(vec!["import './subdir/auto_print_hello.ts';"]),
Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
false,
);
assert!(out.contains("hello!\n"));
}
#[test]
fn eval_unterminated() {
let (out, err) = util::run_and_collect_output(

View file

@ -591,6 +591,7 @@ impl ReplSession {
transform_jsx: false,
jsx_factory: "React.createElement".into(),
jsx_fragment_factory: "React.Fragment".into(),
repl_imports: true,
})?
.0;