diff --git a/cli/diagnostics.rs b/cli/diagnostics.rs index 104a72c909..aaa6a12275 100644 --- a/cli/diagnostics.rs +++ b/cli/diagnostics.rs @@ -1,6 +1,10 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. //! This module encodes TypeScript errors (diagnostics) into Rust structs and //! contains code for printing them to the console. + +// TODO(ry) This module does a lot of JSON parsing manually. It should use +// serde_json. + use crate::colors; use crate::fmt_errors::format_maybe_source_line; use crate::fmt_errors::format_maybe_source_name; @@ -29,7 +33,7 @@ impl Diagnostic { let items_values = items_v.as_array().unwrap(); for item_v in items_values { - items.push(DiagnosticItem::from_json_value(item_v)); + items.push(DiagnosticItem::from_json_value(item_v)?); } } @@ -114,14 +118,13 @@ pub struct DiagnosticItem { } impl DiagnosticItem { - pub fn from_json_value(v: &serde_json::Value) -> Self { + pub fn from_json_value(v: &serde_json::Value) -> Option { let obj = v.as_object().unwrap(); // required attributes let message = obj .get("message") - .and_then(|v| v.as_str().map(String::from)) - .unwrap(); + .and_then(|v| v.as_str().map(String::from))?; let category = DiagnosticCategory::from( obj.get("category").and_then(Value::as_i64).unwrap(), ); @@ -154,7 +157,7 @@ impl DiagnosticItem { for related_info_v in related_info_values { related_information - .push(DiagnosticItem::from_json_value(related_info_v)); + .push(DiagnosticItem::from_json_value(related_info_v)?); } Some(related_information) @@ -162,7 +165,7 @@ impl DiagnosticItem { _ => None, }; - Self { + Some(Self { message, message_chain, related_information, @@ -175,7 +178,7 @@ impl DiagnosticItem { category, start_column, end_column, - } + }) } } diff --git a/cli/js/deno.ts b/cli/js/deno.ts index 887f608260..f5f7741799 100644 --- a/cli/js/deno.ts +++ b/cli/js/deno.ts @@ -43,6 +43,7 @@ export { OpenOptions, OpenMode } from "./files.ts"; +export { formatDiagnostics } from "./format_error.ts"; export { FsEvent, fsEvents } from "./fs_events.ts"; export { EOF, diff --git a/cli/js/dispatch.ts b/cli/js/dispatch.ts index fc1c69a62d..1b7e234632 100644 --- a/cli/js/dispatch.ts +++ b/cli/js/dispatch.ts @@ -17,6 +17,7 @@ export let OP_GET_DIR: number; export let OP_START: number; export let OP_APPLY_SOURCE_MAP: number; export let OP_FORMAT_ERROR: number; +export let OP_FORMAT_DIAGNOSTIC: number; export let OP_CACHE: number; export let OP_RESOLVE_MODULES: number; export let OP_FETCH_ASSET: number; diff --git a/cli/js/format_error.ts b/cli/js/format_error.ts index d1d5a73f21..63250473b1 100644 --- a/cli/js/format_error.ts +++ b/cli/js/format_error.ts @@ -1,4 +1,5 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { DiagnosticItem } from "./diagnostics.ts"; import * as dispatch from "./dispatch.ts"; import { sendSync } from "./dispatch_json.ts"; @@ -7,3 +8,11 @@ export function formatError(errString: string): string { const res = sendSync(dispatch.OP_FORMAT_ERROR, { error: errString }); return res.error; } + +/** + * Format an array of diagnostic items and return them as a single string. + * @param items An array of diagnostic items to format + */ +export function formatDiagnostics(items: DiagnosticItem[]): string { + return sendSync(dispatch.OP_FORMAT_DIAGNOSTIC, { items }); +} diff --git a/cli/js/format_error_test.ts b/cli/js/format_error_test.ts new file mode 100644 index 0000000000..282c2b274b --- /dev/null +++ b/cli/js/format_error_test.ts @@ -0,0 +1,37 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { assert, test } from "./test_util.ts"; + +test(function formatDiagnosticBasic() { + const fixture: Deno.DiagnosticItem[] = [ + { + message: "Example error", + category: Deno.DiagnosticCategory.Error, + sourceLine: "abcdefghijklmnopqrstuv", + lineNumber: 1000, + scriptResourceName: "foo.ts", + startColumn: 1, + endColumn: 2, + code: 4000 + } + ]; + const out = Deno.formatDiagnostics(fixture); + assert(out.includes("Example error")); + assert(out.includes("foo.ts")); +}); + +test(function formatDiagnosticError() { + let thrown = false; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const bad = ([{ hello: 123 }] as any) as Deno.DiagnosticItem[]; + try { + Deno.formatDiagnostics(bad); + } catch (e) { + assert(e instanceof TypeError); + thrown = true; + } + assert(thrown); +}); + +if (import.meta.main) { + Deno.runTests(); +} diff --git a/cli/js/lib.deno.ns.d.ts b/cli/js/lib.deno.ns.d.ts index 0c94308691..4d7822c907 100644 --- a/cli/js/lib.deno.ns.d.ts +++ b/cli/js/lib.deno.ns.d.ts @@ -1901,6 +1901,13 @@ declare namespace Deno { items: DiagnosticItem[]; } + /** UNSTABLE: new API, yet to be vetted. + * + * Format an array of diagnostic items and return them as a single string. + * @param items An array of diagnostic items to format + */ + export function formatDiagnostics(items: DiagnosticItem[]): string; + /** UNSTABLE: new API, yet to be vetted. * * A specific subset TypeScript compiler options that can be supported by diff --git a/cli/js/unit_tests.ts b/cli/js/unit_tests.ts index ec4505c219..1c82374663 100644 --- a/cli/js/unit_tests.ts +++ b/cli/js/unit_tests.ts @@ -23,6 +23,7 @@ import "./fetch_test.ts"; import "./file_test.ts"; import "./files_test.ts"; import "./form_data_test.ts"; +import "./format_error_test.ts"; import "./fs_events_test.ts"; import "./get_random_values_test.ts"; import "./globals_test.ts"; diff --git a/cli/ops/errors.rs b/cli/ops/errors.rs index 4dd4f1ca19..b5cc75f7ad 100644 --- a/cli/ops/errors.rs +++ b/cli/ops/errors.rs @@ -1,5 +1,6 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. use super::dispatch_json::{Deserialize, JsonOp, Value}; +use crate::diagnostics::Diagnostic; use crate::fmt_errors::JSError; use crate::op_error::OpError; use crate::ops::json_op; @@ -18,6 +19,10 @@ pub fn init(i: &mut Isolate, s: &State) { "format_error", s.core_op(json_op(s.stateful_op(op_format_error))), ); + i.register_op( + "format_diagnostic", + s.core_op(json_op(s.stateful_op(op_format_diagnostic))), + ); } #[derive(Deserialize)] @@ -68,3 +73,15 @@ fn op_apply_source_map( "column": orig_column as u32, }))) } + +fn op_format_diagnostic( + _state: &State, + args: Value, + _zero_copy: Option, +) -> Result { + if let Some(diagnostic) = Diagnostic::from_json_value(&args) { + Ok(JsonOp::Sync(json!(diagnostic.to_string()))) + } else { + Err(OpError::type_error("bad diagnostic".to_string())) + } +}