From 7255cc9bc089e1257c2119e4c8a089c0b4970e64 Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) \"Kassimo\" Qian" Date: Fri, 28 Feb 2020 06:17:56 -0800 Subject: [PATCH] Support TypeScript eval through `deno eval -T` flag (#4141) --- cli/file_fetcher.rs | 13 +++++++++ cli/flags.rs | 52 ++++++++++++++++++++++++++++++++-- cli/lib.rs | 36 +++++++++++++++++++++-- cli/tests/030_eval_ts.out | 1 + cli/tests/integration_tests.rs | 6 ++++ 5 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 cli/tests/030_eval_ts.out diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs index 2d9f3c104d..a5c8d3727b 100644 --- a/cli/file_fetcher.rs +++ b/cli/file_fetcher.rs @@ -132,6 +132,19 @@ impl SourceFileFetcher { .ok() } + /// Save a given source file into cache. + /// Allows injection of files that normally would not present + /// in filesystem. + /// This is useful when e.g. TS compiler retrieves a custom file + /// under a dummy specifier. + pub fn save_source_file_in_cache( + &self, + specifier: &ModuleSpecifier, + file: SourceFile, + ) { + self.source_file_cache.set(specifier.to_string(), file); + } + pub async fn fetch_source_file( &self, specifier: &ModuleSpecifier, diff --git a/cli/flags.rs b/cli/flags.rs index b5ed3cddf7..f5a6f0dfdc 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -33,6 +33,7 @@ pub enum DenoSubcommand { }, Eval { code: String, + as_typescript: bool, }, Fetch { files: Vec, @@ -401,7 +402,11 @@ fn eval_parse(flags: &mut Flags, matches: &clap::ArgMatches) { flags.allow_plugin = true; flags.allow_hrtime = true; let code = matches.value_of("code").unwrap().to_string(); - flags.subcommand = DenoSubcommand::Eval { code } + let as_typescript = matches.is_present("ts"); + flags.subcommand = DenoSubcommand::Eval { + code, + as_typescript, + } } fn info_parse(flags: &mut Flags, matches: &clap::ArgMatches) { @@ -662,7 +667,20 @@ fn eval_subcommand<'a, 'b>() -> App<'a, 'b> { This command has implicit access to all permissions (--allow-all) - deno eval \"console.log('hello world')\"", + deno eval \"console.log('hello world')\" + +To evaluate as TypeScript: + + deno eval -T \"const v: string = 'hello'; console.log(v)\" + ", + ) + .arg( + Arg::with_name("ts") + .long("ts") + .short("T") + .help("Treat eval input as TypeScript") + .takes_value(false) + .multiple(false), ) .arg(Arg::with_name("code").takes_value(true).required(true)) .arg(v8_flags_arg()) @@ -1497,6 +1515,34 @@ mod tests { Flags { subcommand: DenoSubcommand::Eval { code: "'console.log(\"hello\")'".to_string(), + as_typescript: false, + }, + allow_net: true, + allow_env: true, + allow_run: true, + allow_read: true, + allow_write: true, + allow_plugin: true, + allow_hrtime: true, + ..Flags::default() + } + ); + } + + #[test] + fn eval_typescript() { + let r = flags_from_vec_safe(svec![ + "deno", + "eval", + "-T", + "'console.log(\"hello\")'" + ]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Eval { + code: "'console.log(\"hello\")'".to_string(), + as_typescript: true, }, allow_net: true, allow_env: true, @@ -1519,6 +1565,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Eval { code: "42".to_string(), + as_typescript: false, }, v8_flags: Some(svec!["--help"]), allow_net: true, @@ -2131,6 +2178,7 @@ fn eval_with_cafile() { Flags { subcommand: DenoSubcommand::Eval { code: "console.log('hello world')".to_string(), + as_typescript: false, }, ca_file: Some("example.crt".to_owned()), allow_net: true, diff --git a/cli/lib.rs b/cli/lib.rs index 32d583a224..a4d4ec331d 100644 --- a/cli/lib.rs +++ b/cli/lib.rs @@ -57,8 +57,10 @@ mod web_worker; pub mod worker; use crate::compilers::TargetLib; +use crate::file_fetcher::SourceFile; use crate::fs as deno_fs; use crate::global_state::GlobalState; +use crate::msg::MediaType; use crate::ops::io::get_stdio; use crate::state::State; use crate::worker::MainWorker; @@ -279,14 +281,39 @@ async fn fetch_command(flags: Flags, files: Vec) -> Result<(), ErrBox> { Ok(()) } -async fn eval_command(flags: Flags, code: String) -> Result<(), ErrBox> { +async fn eval_command( + flags: Flags, + code: String, + as_typescript: bool, +) -> Result<(), ErrBox> { // Force TypeScript compile. let main_module = ModuleSpecifier::resolve_url_or_path("./__$deno$eval.ts").unwrap(); let global_state = GlobalState::new(flags)?; let mut worker = create_main_worker(global_state, main_module.clone())?; + let main_module_url = main_module.as_url().to_owned(); + // Create a dummy source file. + let source_file = SourceFile { + filename: main_module_url.to_file_path().unwrap(), + url: main_module_url, + types_url: None, + media_type: if as_typescript { + MediaType::TypeScript + } else { + MediaType::JavaScript + }, + source_code: code.clone().into_bytes(), + }; + // Save our fake file into file fetcher cache + // to allow module access by TS compiler (e.g. op_fetch_source_files) + worker + .state + .borrow() + .global_state + .file_fetcher + .save_source_file_in_cache(&main_module, source_file); debug!("main_module {}", &main_module); - worker.execute_module_from_code(&main_module, code).await?; + worker.execute_module(&main_module).await?; worker.execute("window.dispatchEvent(new Event('load'))")?; (&mut *worker).await?; worker.execute("window.dispatchEvent(new Event('unload'))")?; @@ -409,7 +436,10 @@ pub fn main() { source_file, out_file, } => bundle_command(flags, source_file, out_file).boxed_local(), - DenoSubcommand::Eval { code } => eval_command(flags, code).boxed_local(), + DenoSubcommand::Eval { + code, + as_typescript, + } => eval_command(flags, code, as_typescript).boxed_local(), DenoSubcommand::Fetch { files } => { fetch_command(flags, files).boxed_local() } diff --git a/cli/tests/030_eval_ts.out b/cli/tests/030_eval_ts.out new file mode 100644 index 0000000000..190a18037c --- /dev/null +++ b/cli/tests/030_eval_ts.out @@ -0,0 +1 @@ +123 diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index a326af1e2f..843f56a7fc 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -788,6 +788,12 @@ itest!(_029_eval { output: "029_eval.out", }); +// Ugly parentheses due to whitespace delimiting problem. +itest!(_030_eval_ts { + args: "eval -T console.log((123)as(number))", // 'as' is a TS keyword only + output: "030_eval_ts.out", +}); + itest!(_033_import_map { args: "run --reload --importmap=importmaps/import_map.json importmaps/test.ts",