From 0c5900b141564e748c01516ddfba987baa4f0290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 15 Sep 2020 14:51:31 +0200 Subject: [PATCH] refactor(unstable): change return types in runtime compiler APIs --- cli/diagnostics.rs | 32 +++++++++++++++-- cli/dts/lib.deno.unstable.d.ts | 24 +++++++++---- cli/ops/runtime_compiler.rs | 17 ++++----- cli/rt/40_compiler_api.js | 33 ++++++++---------- cli/tests/compiler_api_test.ts | 64 +++++++++++++++++----------------- cli/tests/lib_ref.ts | 6 ++-- cli/tests/lib_runtime_api.ts | 6 ++-- cli/tsc.rs | 50 +++++++++++--------------- docs/runtime/compiler_apis.md | 10 +++--- 9 files changed, 135 insertions(+), 107 deletions(-) diff --git a/cli/diagnostics.rs b/cli/diagnostics.rs index 06fc585bbd1174..7179b16df7a35d 100644 --- a/cli/diagnostics.rs +++ b/cli/diagnostics.rs @@ -5,6 +5,8 @@ use crate::colors; use regex::Regex; use serde::Deserialize; use serde::Deserializer; +use serde::Serialize; +use serde::Serializer; use std::error::Error; use std::fmt; @@ -160,6 +162,21 @@ impl<'de> Deserialize<'de> for DiagnosticCategory { } } +impl Serialize for DiagnosticCategory { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let value = match self { + DiagnosticCategory::Warning => 0, + DiagnosticCategory::Error => 1, + DiagnosticCategory::Suggestion => 2, + DiagnosticCategory::Message => 3, + }; + Serialize::serialize(&value, serializer) + } +} + impl From for DiagnosticCategory { fn from(value: i64) -> Self { match value { @@ -172,7 +189,7 @@ impl From for DiagnosticCategory { } } -#[derive(Debug, Deserialize, Clone)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct DiagnosticMessageChain { message_text: String, @@ -199,14 +216,14 @@ impl DiagnosticMessageChain { } } -#[derive(Debug, Deserialize, Clone)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Position { pub line: u64, pub character: u64, } -#[derive(Debug, Deserialize, Clone)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Diagnostic { category: DiagnosticCategory, @@ -359,6 +376,15 @@ impl<'de> Deserialize<'de> for Diagnostics { } } +impl Serialize for Diagnostics { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + Serialize::serialize(&self.0, serializer) + } +} + impl fmt::Display for Diagnostics { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut i = 0; diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts index 04c08b893824b0..7b9e6492d40906 100644 --- a/cli/dts/lib.deno.unstable.d.ts +++ b/cli/dts/lib.deno.unstable.d.ts @@ -236,7 +236,7 @@ declare namespace Deno { * user friendly format. * * ```ts - * const [diagnostics, result] = Deno.compile("file_with_compile_issues.ts"); + * const {diagnostics, emitMap} = Deno.compile("file_with_compile_issues.ts"); * console.table(diagnostics); // Prints raw diagnostic data * console.log(Deno.formatDiagnostics(diagnostics)); // User friendly output of diagnostics * ``` @@ -500,9 +500,15 @@ declare namespace Deno { * the module name will be used to determine the media type of the module. * * ```ts - * const [ maybeDiagnostics1, output1 ] = await Deno.compile("foo.ts"); - * - * const [ maybeDiagnostics2, output2 ] = await Deno.compile("/foo.ts", { + * const { + * diagnostics: maybeDiagnostics1, + * emitMap: output1 + * } = await Deno.compile("foo.ts"); + * + * const { + * diagnostics: maybeDiagnostics2, + * emitMap: output2 + * } = await Deno.compile("/foo.ts", { * "/foo.ts": `export * from "./bar.ts";`, * "/bar.ts": `export const bar = "bar";` * }); @@ -524,7 +530,10 @@ declare namespace Deno { rootName: string, sources?: Record, options?: CompilerOptions, - ): Promise<[Diagnostic[] | undefined, Record]>; + ): Promise<{ + diagnostics: Diagnostic[] | undefined, + emitMap: Record + }>; /** **UNSTABLE**: new API, yet to be vetted. * @@ -567,7 +576,10 @@ declare namespace Deno { rootName: string, sources?: Record, options?: CompilerOptions, - ): Promise<[Diagnostic[] | undefined, string]>; + ): Promise<{ + diagnostics: Diagnostic[] | undefined, + output: string + }>; /** **UNSTABLE**: Should not have same name as `window.location` type. */ interface Location { diff --git a/cli/ops/runtime_compiler.rs b/cli/ops/runtime_compiler.rs index 0b78f39d1e30fd..7e328569d5ad36 100644 --- a/cli/ops/runtime_compiler.rs +++ b/cli/ops/runtime_compiler.rs @@ -1,6 +1,5 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -use crate::futures::FutureExt; use crate::tsc::runtime_bundle; use crate::tsc::runtime_compile; use crate::tsc::runtime_transpile; @@ -37,27 +36,29 @@ async fn op_compile( let args: CompileArgs = serde_json::from_value(args)?; let global_state = cli_state.global_state.clone(); let permissions = cli_state.permissions.borrow().clone(); - let fut = if args.bundle { - runtime_bundle( + let response = if args.bundle { + let r = runtime_bundle( &global_state, permissions, &args.root_name, &args.sources, &args.options, ) - .boxed_local() + .await?; + serde_json::to_value(r)? } else { - runtime_compile( + let r = runtime_compile( &global_state, permissions, &args.root_name, &args.sources, &args.options, ) - .boxed_local() + .await?; + serde_json::to_value(r)? }; - let result = fut.await?; - Ok(result) + + Ok(response) } #[derive(Deserialize, Debug)] diff --git a/cli/rt/40_compiler_api.js b/cli/rt/40_compiler_api.js index 8a2aa759a57f7b..be0f119368aec7 100644 --- a/cli/rt/40_compiler_api.js +++ b/cli/rt/40_compiler_api.js @@ -35,7 +35,6 @@ return opTranspile(payload); } - // TODO(bartlomieju): change return type to interface? async function compile( rootName, sources, @@ -52,22 +51,20 @@ sources: !!sources, options, }); - const result = await opCompile(payload); - util.assert(result.emitMap); - const maybeDiagnostics = result.diagnostics.length === 0 - ? undefined - : result.diagnostics; - - const emitMap = {}; + const { diagnostics, emitMap } = await opCompile(payload); + util.assert(emitMap); - for (const [key, emittedSource] of Object.entries(result.emitMap)) { - emitMap[key] = emittedSource.contents; + const processedEmitMap = {}; + for (const [key, emittedSource] of Object.entries(emitMap)) { + processedEmitMap[key] = emittedSource.contents; } - return [maybeDiagnostics, emitMap]; + return { + diagnostics: diagnostics.length === 0 ? undefined : diagnostics, + emitMap: processedEmitMap, + }; } - // TODO(bartlomieju): change return type to interface? async function bundle( rootName, sources, @@ -84,12 +81,12 @@ sources: !!sources, options, }); - const result = await opCompile(payload); - util.assert(result.output); - const maybeDiagnostics = result.diagnostics.length === 0 - ? undefined - : result.diagnostics; - return [maybeDiagnostics, result.output]; + const { diagnostics, output } = await opCompile(payload); + util.assert(output); + return { + diagnostics: diagnostics.length === 0 ? undefined : diagnostics, + emitMap: processedEmitMap, + }; } window.__bootstrap.compilerApi = { diff --git a/cli/tests/compiler_api_test.ts b/cli/tests/compiler_api_test.ts index b4a2f81ef79441..952e39a4132d9a 100644 --- a/cli/tests/compiler_api_test.ts +++ b/cli/tests/compiler_api_test.ts @@ -8,13 +8,13 @@ import { Deno.test({ name: "Deno.compile() - sources provided", async fn() { - const [diagnostics, actual] = await Deno.compile("/foo.ts", { + const { diagnostics, emitMap } = await Deno.compile("/foo.ts", { "/foo.ts": `import * as bar from "./bar.ts";\n\nconsole.log(bar);\n`, "/bar.ts": `export const bar = "bar";\n`, }); assert(diagnostics == null); - assert(actual); - assertEquals(Object.keys(actual), [ + assert(emitMap); + assertEquals(Object.keys(emitMap), [ "/bar.js.map", "/bar.js", "/foo.js.map", @@ -26,10 +26,10 @@ Deno.test({ Deno.test({ name: "Deno.compile() - no sources provided", async fn() { - const [diagnostics, actual] = await Deno.compile("./subdir/mod1.ts"); + const { diagnostics, emitMap } = await Deno.compile("./subdir/mod1.ts"); assert(diagnostics == null); - assert(actual); - const keys = Object.keys(actual); + assert(emitMap); + const keys = Object.keys(emitMap); assertEquals(keys.length, 6); assert(keys[0].endsWith("print_hello.js.map")); assert(keys[1].endsWith("print_hello.js")); @@ -39,7 +39,7 @@ Deno.test({ Deno.test({ name: "Deno.compile() - compiler options effects emit", async fn() { - const [diagnostics, actual] = await Deno.compile( + const { diagnostics, emitMap } = await Deno.compile( "/foo.ts", { "/foo.ts": `export const foo = "foo";`, @@ -50,16 +50,16 @@ Deno.test({ }, ); assert(diagnostics == null); - assert(actual); - assertEquals(Object.keys(actual), ["/foo.js"]); - assert(actual["/foo.js"].startsWith("define(")); + assert(emitMap); + assertEquals(Object.keys(emitMap), ["/foo.js"]); + assert(emitMap["/foo.js"].startsWith("define(")); }, }); Deno.test({ name: "Deno.compile() - pass lib in compiler options", async fn() { - const [diagnostics, actual] = await Deno.compile( + const { diagnostics, emitMap } = await Deno.compile( "/foo.ts", { "/foo.ts": `console.log(document.getElementById("foo")); @@ -70,15 +70,15 @@ Deno.test({ }, ); assert(diagnostics == null); - assert(actual); - assertEquals(Object.keys(actual), ["/foo.js.map", "/foo.js"]); + assert(emitMap); + assertEquals(Object.keys(emitMap), ["/foo.js.map", "/foo.js"]); }, }); Deno.test({ name: "Deno.compile() - pass outDir in compiler options", async fn() { - const [diagnostics, actual] = await Deno.compile( + const { diagnostics, emitMap } = await Deno.compile( "src/foo.ts", { "src/foo.ts": "console.log('Hello world')", @@ -88,15 +88,15 @@ Deno.test({ }, ); assert(diagnostics == null); - assert(actual); - assertEquals(Object.keys(actual), ["lib/foo.js.map", "lib/foo.js"]); + assert(emitMap); + assertEquals(Object.keys(emitMap), ["lib/foo.js.map", "lib/foo.js"]); }, }); Deno.test({ name: "Deno.compile() - properly handles .d.ts files", async fn() { - const [diagnostics, actual] = await Deno.compile( + const { diagnostics, emitMap } = await Deno.compile( "/foo.ts", { "/foo.ts": `console.log(Foo.bar);`, @@ -106,8 +106,8 @@ Deno.test({ }, ); assert(diagnostics == null); - assert(actual); - assertEquals(Object.keys(actual), ["/foo.js.map", "/foo.js"]); + assert(emitMap); + assertEquals(Object.keys(emitMap), ["/foo.js.map", "/foo.js"]); }, }); @@ -146,30 +146,30 @@ Deno.test({ Deno.test({ name: "Deno.bundle() - sources passed", async fn() { - const [diagnostics, actual] = await Deno.bundle("/foo.ts", { + const { diagnostics, output } = await Deno.bundle("/foo.ts", { "/foo.ts": `export * from "./bar.ts";\n`, "/bar.ts": `export const bar = "bar";\n`, }); assert(diagnostics == null); - assert(actual.includes(`__instantiate("foo", false)`)); - assert(actual.includes(`__exp["bar"]`)); + assert(output.includes(`__instantiate("foo", false)`)); + assert(output.includes(`__exp["bar"]`)); }, }); Deno.test({ name: "Deno.bundle() - no sources passed", async fn() { - const [diagnostics, actual] = await Deno.bundle("./subdir/mod1.ts"); + const { diagnostics, output } = await Deno.bundle("./subdir/mod1.ts"); assert(diagnostics == null); - assert(actual.includes(`__instantiate("mod1", false)`)); - assert(actual.includes(`__exp["printHello3"]`)); + assert(output.includes(`__instantiate("mod1", false)`)); + assert(output.includes(`__exp["printHello3"]`)); }, }); Deno.test({ name: "Deno.bundle() - compiler config effects emit", async fn() { - const [diagnostics, actual] = await Deno.bundle( + const { diagnostics, output } = await Deno.bundle( "/foo.ts", { "/foo.ts": `// random comment\nexport * from "./bar.ts";\n`, @@ -180,26 +180,26 @@ Deno.test({ }, ); assert(diagnostics == null); - assert(!actual.includes(`random`)); + assert(!output.includes(`random`)); }, }); Deno.test({ name: "Deno.bundle() - JS Modules included", async fn() { - const [diagnostics, actual] = await Deno.bundle("/foo.js", { + const { diagnostics, output } = await Deno.bundle("/foo.js", { "/foo.js": `export * from "./bar.js";\n`, "/bar.js": `export const bar = "bar";\n`, }); assert(diagnostics == null); - assert(actual.includes(`System.register("bar",`)); + assert(output.includes(`System.register("bar",`)); }, }); Deno.test({ name: "Deno.bundle - pre ES2017 uses ES5 loader", async fn() { - const [diagnostics, actual] = await Deno.bundle( + const { diagnostics, output } = await Deno.bundle( "/foo.ts", { "/foo.ts": `console.log("hello world!")\n`, @@ -207,14 +207,14 @@ Deno.test({ { target: "es2015" }, ); assert(diagnostics == null); - assert(actual.includes(`var __awaiter = `)); + assert(output.includes(`var __awaiter = `)); }, }); Deno.test({ name: "runtime compiler APIs diagnostics", async fn() { - const [diagnostics] = await Deno.compile("/foo.ts", { + const { diagnostics } = await Deno.compile("/foo.ts", { "/foo.ts": `document.getElementById("foo");`, }); assert(Array.isArray(diagnostics)); diff --git a/cli/tests/lib_ref.ts b/cli/tests/lib_ref.ts index 0a4ce3fc72062e..d73a09ca414cdc 100644 --- a/cli/tests/lib_ref.ts +++ b/cli/tests/lib_ref.ts @@ -1,4 +1,4 @@ -const [errors, program] = await Deno.compile( +const { diagnostics, emitMap } = await Deno.compile( "main.ts", { "main.ts": @@ -10,5 +10,5 @@ const [errors, program] = await Deno.compile( }, ); -console.log(errors); -console.log(Object.keys(program)); +console.log(diagnostics); +console.log(Object.keys(emitMap)); diff --git a/cli/tests/lib_runtime_api.ts b/cli/tests/lib_runtime_api.ts index 788288f72d8440..9b58ebcda41ee9 100644 --- a/cli/tests/lib_runtime_api.ts +++ b/cli/tests/lib_runtime_api.ts @@ -1,4 +1,4 @@ -const [errors, program] = await Deno.compile( +const { diagnostics, emitMap } = await Deno.compile( "main.ts", { "main.ts": `document.getElementById("foo");`, @@ -8,5 +8,5 @@ const [errors, program] = await Deno.compile( }, ); -console.log(errors); -console.log(Object.keys(program)); +console.log(diagnostics); +console.log(Object.keys(emitMap)); diff --git a/cli/tsc.rs b/cli/tsc.rs index 888c6fdeb9d251..fc53ae162db132 100644 --- a/cli/tsc.rs +++ b/cli/tsc.rs @@ -385,11 +385,11 @@ struct Stat { value: f64, } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -struct EmittedSource { - filename: String, - contents: String, +pub struct EmittedSource { + pub filename: String, + pub contents: String, } #[derive(Deserialize)] @@ -419,20 +419,18 @@ struct TranspileTsOptions { jsx_fragment_factory: String, } -// TODO(bartlomieju): possible deduplicate once TS refactor is stabilized -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -#[allow(unused)] -struct RuntimeBundleResponse { - diagnostics: Diagnostics, - output: String, +pub struct RuntimeBundleResponse { + pub diagnostics: Diagnostics, + pub output: String, } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -struct RuntimeCompileResponse { - diagnostics: Diagnostics, - emit_map: HashMap, +pub struct RuntimeCompileResponse { + pub diagnostics: Diagnostics, + pub emit_map: HashMap, } impl TsCompiler { @@ -655,7 +653,7 @@ impl TsCompiler { if let Some(build_info) = compile_response.build_info { self.cache_build_info(&module_url, build_info)?; } - self.cache_emitted_files(compile_response.emit_map)?; + self.cache_emitted_files(&compile_response.emit_map)?; Ok(()) } @@ -847,7 +845,7 @@ impl TsCompiler { emit_map.insert(emitted_filename, emitted_source); } - self.cache_emitted_files(emit_map)?; + self.cache_emitted_files(&emit_map)?; Ok(()) } @@ -886,7 +884,7 @@ impl TsCompiler { fn cache_emitted_files( &self, - emit_map: HashMap, + emit_map: &HashMap, ) -> std::io::Result<()> { for (emitted_name, source) in emit_map.iter() { let specifier = ModuleSpecifier::resolve_url(&source.filename) @@ -1199,7 +1197,7 @@ pub async fn runtime_compile( root_name: &str, sources: &Option>, maybe_options: &Option, -) -> Result { +) -> Result { let mut user_options = if let Some(options) = maybe_options { tsc_config::parse_raw_config(options)? } else { @@ -1285,13 +1283,10 @@ pub async fn runtime_compile( let response: RuntimeCompileResponse = serde_json::from_str(&json_str)?; if response.diagnostics.0.is_empty() && sources.is_none() { - compiler.cache_emitted_files(response.emit_map)?; + compiler.cache_emitted_files(&response.emit_map)?; } - // We're returning `Ok()` instead of `Err()` because it's not runtime - // error if there were diagnostics produced; we want to let user handle - // diagnostics in the runtime. - Ok(serde_json::from_str::(&json_str).unwrap()) + Ok(response) } /// This function is used by `Deno.bundle()` API. @@ -1301,7 +1296,7 @@ pub async fn runtime_bundle( root_name: &str, sources: &Option>, maybe_options: &Option, -) -> Result { +) -> Result { let mut user_options = if let Some(options) = maybe_options { tsc_config::parse_raw_config(options)? } else { @@ -1390,11 +1385,8 @@ pub async fn runtime_bundle( let json_str = execute_in_same_thread(global_state, permissions, req_msg) .await .map_err(js_error_to_errbox)?; - let _response: RuntimeBundleResponse = serde_json::from_str(&json_str)?; - // We're returning `Ok()` instead of `Err()` because it's not runtime - // error if there were diagnostics produced; we want to let user handle - // diagnostics in the runtime. - Ok(serde_json::from_str::(&json_str).unwrap()) + let response: RuntimeBundleResponse = serde_json::from_str(&json_str)?; + Ok(response) } /// This function is used by `Deno.transpileOnly()` API. diff --git a/docs/runtime/compiler_apis.md b/docs/runtime/compiler_apis.md index 3424c2b5fda7ac..0bbb8bb9f46693 100644 --- a/docs/runtime/compiler_apis.md +++ b/docs/runtime/compiler_apis.md @@ -31,7 +31,7 @@ the keys are the output filenames and the values are the content. An example of providing sources: ```ts -const [diagnostics, emitMap] = await Deno.compile("/foo.ts", { +const { diagnostics, emitMap } = await Deno.compile("/foo.ts", { "/foo.ts": `import * as bar from "./bar.ts";\nconsole.log(bar);\n`, "/bar.ts": `export const bar = "bar";\n`, }); @@ -47,7 +47,7 @@ When not supplying resources, you can use local or remote modules, just like you could do on the command line. So you could do something like this: ```ts -const [diagnostics, emitMap] = await Deno.compile( +const { diagnostics, emitMap } = await Deno.compile( "https://deno.land/std@$STD_VERSION/examples/welcome.ts", ); ``` @@ -144,7 +144,7 @@ that is destined for the browser, you would want to use the TypeScript `"dom"` library: ```ts -const [errors, emitted] = await Deno.compile( +const { diagnostics, emitMap } = await Deno.compile( "main.ts", { "main.ts": `document.getElementById("foo");\n`, @@ -184,7 +184,7 @@ So to add the Deno namespace to a compilation, you would include the `deno.ns` lib in the array. For example: ```ts -const [errors, emitted] = await Deno.compile( +const { diagnostics, emitMap } = await Deno.compile( "main.ts", { "main.ts": `document.getElementById("foo");\n`, @@ -215,7 +215,7 @@ document.getElementById("foo"); It would compile without errors like this: ```ts -const [errors, emitted] = await Deno.compile("./main.ts", undefined, { +const { diagnostics, emitMap } = await Deno.compile("./main.ts", undefined, { lib: ["esnext"], }); ```