summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/BUILD.gn1
-rw-r--r--cli/ansi.rs26
-rw-r--r--cli/compiler.rs19
-rw-r--r--cli/diagnostics.rs668
-rw-r--r--cli/main.rs1
-rw-r--r--cli/worker.rs3
6 files changed, 707 insertions, 11 deletions
diff --git a/cli/BUILD.gn b/cli/BUILD.gn
index 7887624e2..45386f320 100644
--- a/cli/BUILD.gn
+++ b/cli/BUILD.gn
@@ -74,6 +74,7 @@ ts_sources = [
"../js/core.ts",
"../js/custom_event.ts",
"../js/deno.ts",
+ "../js/diagnostics.ts",
"../js/dir.ts",
"../js/dispatch.ts",
"../js/dispatch_minimal.ts",
diff --git a/cli/ansi.rs b/cli/ansi.rs
index 95b5e0694..b9e9fe123 100644
--- a/cli/ansi.rs
+++ b/cli/ansi.rs
@@ -1,6 +1,8 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+use ansi_term::Color::Black;
use ansi_term::Color::Fixed;
use ansi_term::Color::Red;
+use ansi_term::Color::White;
use ansi_term::Style;
use regex::Regex;
use std::env;
@@ -43,6 +45,14 @@ pub fn italic_bold(s: String) -> impl fmt::Display {
style.paint(s)
}
+pub fn black_on_white(s: String) -> impl fmt::Display {
+ let mut style = Style::new();
+ if use_color() {
+ style = style.on(White).fg(Black);
+ }
+ style.paint(s)
+}
+
pub fn yellow(s: String) -> impl fmt::Display {
let mut style = Style::new();
if use_color() {
@@ -61,6 +71,22 @@ pub fn cyan(s: String) -> impl fmt::Display {
style.paint(s)
}
+pub fn red(s: String) -> impl fmt::Display {
+ let mut style = Style::new();
+ if use_color() {
+ style = style.fg(Red);
+ }
+ style.paint(s)
+}
+
+pub fn grey(s: String) -> impl fmt::Display {
+ let mut style = Style::new();
+ if use_color() {
+ style = style.fg(Fixed(8));
+ }
+ style.paint(s)
+}
+
pub fn bold(s: String) -> impl fmt::Display {
let mut style = Style::new();
if use_color() {
diff --git a/cli/compiler.rs b/cli/compiler.rs
index e1bb56130..0b6c278f9 100644
--- a/cli/compiler.rs
+++ b/cli/compiler.rs
@@ -1,4 +1,5 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+use crate::diagnostics::Diagnostic;
use crate::msg;
use crate::resources;
use crate::startup_data;
@@ -7,7 +8,6 @@ use crate::tokio_util;
use crate::worker::Worker;
use deno::js_check;
use deno::Buf;
-use deno::JSError;
use futures::Future;
use futures::Stream;
use std::str;
@@ -87,7 +87,7 @@ pub fn compile_async(
specifier: &str,
referrer: &str,
module_meta_data: &ModuleMetaData,
-) -> impl Future<Item = ModuleMetaData, Error = JSError> {
+) -> impl Future<Item = ModuleMetaData, Error = Diagnostic> {
debug!(
"Running rust part of compile_sync. specifier: {}, referrer: {}",
&specifier, &referrer
@@ -136,14 +136,15 @@ pub fn compile_async(
first_msg_fut
.map_err(|_| panic!("not handled"))
.and_then(move |maybe_msg: Option<Buf>| {
- let _res_msg = maybe_msg.unwrap();
-
debug!("Received message from worker");
- // TODO res is EmitResult, use serde_derive to parse it. Errors from the
- // worker or Diagnostics should be somehow forwarded to the caller!
- // Currently they are handled inside compiler.ts with os.exit(1) and above
- // with std::process::exit(1). This bad.
+ if let Some(msg) = maybe_msg {
+ let json_str = std::str::from_utf8(&msg).unwrap();
+ debug!("Message: {}", json_str);
+ if let Some(diagnostics) = Diagnostic::from_emit_result(json_str) {
+ return Err(diagnostics);
+ }
+ }
let r = state.dir.fetch_module_meta_data(
&module_meta_data_.module_name,
@@ -169,7 +170,7 @@ pub fn compile_sync(
specifier: &str,
referrer: &str,
module_meta_data: &ModuleMetaData,
-) -> Result<ModuleMetaData, JSError> {
+) -> Result<ModuleMetaData, Diagnostic> {
tokio_util::block_on(compile_async(
state,
specifier,
diff --git a/cli/diagnostics.rs b/cli/diagnostics.rs
new file mode 100644
index 000000000..af384f277
--- /dev/null
+++ b/cli/diagnostics.rs
@@ -0,0 +1,668 @@
+// Copyright 2018-2019 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.
+use crate::ansi;
+use serde_json;
+use serde_json::value::Value;
+use std::fmt;
+
+// A trait which specifies parts of a diagnostic like item needs to be able to
+// generate to conform its display to other diagnostic like items
+pub trait DisplayFormatter {
+ fn format_category_and_code(&self) -> String;
+ fn format_message(&self, level: usize) -> String;
+ fn format_related_info(&self) -> String;
+ fn format_source_line(&self, level: usize) -> String;
+ fn format_source_name(&self, level: usize) -> String;
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct Diagnostic {
+ pub items: Vec<DiagnosticItem>,
+}
+
+impl Diagnostic {
+ /// Take a JSON value and attempt to map it to a
+ pub fn from_json_value(v: &serde_json::Value) -> Option<Self> {
+ if !v.is_object() {
+ return None;
+ }
+ let obj = v.as_object().unwrap();
+
+ let mut items = Vec::<DiagnosticItem>::new();
+ let items_v = &obj["items"];
+ if items_v.is_array() {
+ let items_values = items_v.as_array().unwrap();
+
+ for item_v in items_values {
+ items.push(DiagnosticItem::from_json_value(item_v));
+ }
+ }
+
+ Some(Self { items })
+ }
+
+ pub fn from_emit_result(json_str: &str) -> Option<Self> {
+ let v = serde_json::from_str::<serde_json::Value>(json_str)
+ .expect("Error decoding JSON string.");
+ let diagnostics_o = v.get("diagnostics");
+ if let Some(diagnostics_v) = diagnostics_o {
+ return Self::from_json_value(diagnostics_v);
+ }
+
+ None
+ }
+}
+
+impl fmt::Display for Diagnostic {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut i = 0;
+ for item in &self.items {
+ if i > 0 {
+ writeln!(f)?;
+ }
+ write!(f, "{}", item.to_string())?;
+ i += 1;
+ }
+
+ if i > 1 {
+ write!(f, "\n\nFound {} errors.\n", i)?;
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct DiagnosticItem {
+ /// The top level message relating to the diagnostic item.
+ pub message: String,
+
+ /// A chain of messages, code, and categories of messages which indicate the
+ /// full diagnostic information.
+ pub message_chain: Option<Box<DiagnosticMessageChain>>,
+
+ /// Other diagnostic items that are related to the diagnostic, usually these
+ /// are suggestions of why an error occurred.
+ pub related_information: Option<Vec<DiagnosticItem>>,
+
+ /// The source line the diagnostic is in reference to.
+ pub source_line: Option<String>,
+
+ /// Zero-based index to the line number of the error.
+ pub line_number: Option<i64>,
+
+ /// The resource name provided to the TypeScript compiler.
+ pub script_resource_name: Option<String>,
+
+ /// Zero-based index to the start position in the entire script resource.
+ pub start_position: Option<i64>,
+
+ /// Zero-based index to the end position in the entire script resource.
+ pub end_position: Option<i64>,
+ pub category: DiagnosticCategory,
+
+ /// This is defined in TypeScript and can be referenced via
+ /// [diagnosticMessages.json](https://github.com/microsoft/TypeScript/blob/master/src/compiler/diagnosticMessages.json).
+ pub code: i64,
+
+ /// Zero-based index to the start column on `line_number`.
+ pub start_column: Option<i64>,
+
+ /// Zero-based index to the end column on `line_number`.
+ pub end_column: Option<i64>,
+}
+
+impl DiagnosticItem {
+ pub fn from_json_value(v: &serde_json::Value) -> Self {
+ let obj = v.as_object().unwrap();
+
+ // required attributes
+ let message = obj
+ .get("message")
+ .and_then(|v| v.as_str().map(String::from))
+ .unwrap();
+ let category = DiagnosticCategory::from(
+ obj.get("category").and_then(Value::as_i64).unwrap(),
+ );
+ let code = obj.get("code").and_then(Value::as_i64).unwrap();
+
+ // optional attributes
+ let source_line = obj
+ .get("sourceLine")
+ .and_then(|v| v.as_str().map(String::from));
+ let script_resource_name = obj
+ .get("scriptResourceName")
+ .and_then(|v| v.as_str().map(String::from));
+ let line_number = obj.get("lineNumber").and_then(Value::as_i64);
+ let start_position = obj.get("startPosition").and_then(Value::as_i64);
+ let end_position = obj.get("endPosition").and_then(Value::as_i64);
+ let start_column = obj.get("startColumn").and_then(Value::as_i64);
+ let end_column = obj.get("endColumn").and_then(Value::as_i64);
+
+ let message_chain_v = obj.get("messageChain");
+ let message_chain = match message_chain_v {
+ Some(v) => DiagnosticMessageChain::from_json_value(v),
+ _ => None,
+ };
+
+ let related_information_v = obj.get("relatedInformation");
+ let related_information = match related_information_v {
+ Some(r) => {
+ let mut related_information = Vec::<DiagnosticItem>::new();
+ let related_info_values = r.as_array().unwrap();
+
+ for related_info_v in related_info_values {
+ related_information
+ .push(DiagnosticItem::from_json_value(related_info_v));
+ }
+
+ Some(related_information)
+ }
+ _ => None,
+ };
+
+ Self {
+ message,
+ message_chain,
+ related_information,
+ code,
+ source_line,
+ script_resource_name,
+ line_number,
+ start_position,
+ end_position,
+ category,
+ start_column,
+ end_column,
+ }
+ }
+}
+
+// TODO should chare logic with cli/js_errors, possibly with JSError
+// implementing the `DisplayFormatter` trait.
+impl DisplayFormatter for DiagnosticItem {
+ fn format_category_and_code(&self) -> String {
+ let category = match self.category {
+ DiagnosticCategory::Error => {
+ format!("- {}", ansi::red("error".to_string()))
+ }
+ DiagnosticCategory::Warning => "- warn".to_string(),
+ DiagnosticCategory::Debug => "- debug".to_string(),
+ DiagnosticCategory::Info => "- info".to_string(),
+ _ => "".to_string(),
+ };
+
+ let code = ansi::grey(format!(" TS{}:", self.code.to_string())).to_string();
+
+ format!("{}{} ", category, code)
+ }
+
+ fn format_message(&self, level: usize) -> String {
+ if self.message_chain.is_none() {
+ return format!("{:indent$}{}", "", self.message, indent = level);
+ }
+
+ let mut s = String::new();
+ let mut i = level / 2;
+ let mut item_o = self.message_chain.clone();
+ while item_o.is_some() {
+ let item = item_o.unwrap();
+ s.push_str(&std::iter::repeat(" ").take(i * 2).collect::<String>());
+ s.push_str(&item.message);
+ s.push('\n');
+ item_o = item.next.clone();
+ i += 1;
+ }
+ s.pop();
+
+ s
+ }
+
+ fn format_related_info(&self) -> String {
+ if self.related_information.is_none() {
+ return "".to_string();
+ }
+
+ let mut s = String::new();
+ let related_information = self.related_information.clone().unwrap();
+ for related_diagnostic in related_information {
+ let rd = &related_diagnostic;
+ s.push_str(&format!(
+ "\n{}{}{}\n",
+ rd.format_source_name(2),
+ rd.format_source_line(4),
+ rd.format_message(4),
+ ));
+ }
+
+ s
+ }
+
+ fn format_source_line(&self, level: usize) -> String {
+ if self.source_line.is_none() {
+ return "".to_string();
+ }
+
+ let source_line = self.source_line.as_ref().unwrap();
+ // sometimes source_line gets set with an empty string, which then outputs
+ // an empty source line when displayed, so need just short circuit here
+ if source_line.is_empty() {
+ return "".to_string();
+ }
+
+ assert!(self.line_number.is_some());
+ assert!(self.start_column.is_some());
+ assert!(self.end_column.is_some());
+ let line = (1 + self.line_number.unwrap()).to_string();
+ let line_color = ansi::black_on_white(line.to_string());
+ let line_len = line.clone().len();
+ let line_padding =
+ ansi::black_on_white(format!("{:indent$}", "", indent = line_len))
+ .to_string();
+ let mut s = String::new();
+ let start_column = self.start_column.unwrap();
+ let end_column = self.end_column.unwrap();
+ // TypeScript uses `~` always, but V8 would utilise `^` always, even when
+ // doing ranges, so here, if we only have one marker (very common with V8
+ // errors) we will use `^` instead.
+ let underline_char = if (end_column - start_column) <= 1 {
+ '^'
+ } else {
+ '~'
+ };
+ for i in 0..end_column {
+ if i >= start_column {
+ s.push(underline_char);
+ } else {
+ s.push(' ');
+ }
+ }
+ let color_underline = match self.category {
+ DiagnosticCategory::Error => ansi::red(s).to_string(),
+ _ => ansi::cyan(s).to_string(),
+ };
+
+ let indent = format!("{:indent$}", "", indent = level);
+
+ format!(
+ "\n\n{}{} {}\n{}{} {}\n",
+ indent, line_color, source_line, indent, line_padding, color_underline
+ )
+ }
+
+ fn format_source_name(&self, level: usize) -> String {
+ if self.script_resource_name.is_none() {
+ return "".to_string();
+ }
+
+ let script_name = ansi::cyan(self.script_resource_name.clone().unwrap());
+ assert!(self.line_number.is_some());
+ assert!(self.start_column.is_some());
+ let line = ansi::yellow((1 + self.line_number.unwrap()).to_string());
+ let column = ansi::yellow((1 + self.start_column.unwrap()).to_string());
+ format!(
+ "{:indent$}{}:{}:{} ",
+ "",
+ script_name,
+ line,
+ column,
+ indent = level
+ )
+ }
+}
+
+impl fmt::Display for DiagnosticItem {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "{}{}{}{}{}",
+ self.format_source_name(0),
+ self.format_category_and_code(),
+ self.format_message(0),
+ self.format_source_line(0),
+ self.format_related_info(),
+ )?;
+
+ Ok(())
+ }
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct DiagnosticMessageChain {
+ pub message: String,
+ pub code: i64,
+ pub category: DiagnosticCategory,
+ pub next: Option<Box<DiagnosticMessageChain>>,
+}
+
+impl DiagnosticMessageChain {
+ pub fn from_json_value(v: &serde_json::Value) -> Option<Box<Self>> {
+ if !v.is_object() {
+ return None;
+ }
+
+ let obj = v.as_object().unwrap();
+ let message = obj
+ .get("message")
+ .and_then(|v| v.as_str().map(String::from))
+ .unwrap();
+ let code = obj.get("code").and_then(Value::as_i64).unwrap();
+ let category = DiagnosticCategory::from(
+ obj.get("category").and_then(Value::as_i64).unwrap(),
+ );
+
+ let next_v = obj.get("next");
+ let next = match next_v {
+ Some(n) => DiagnosticMessageChain::from_json_value(n),
+ _ => None,
+ };
+
+ Some(Box::new(Self {
+ message,
+ code,
+ category,
+ next,
+ }))
+ }
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub enum DiagnosticCategory {
+ Log, // 0
+ Debug, // 1
+ Info, // 2
+ Error, // 3
+ Warning, // 4
+ Suggestion, // 5
+}
+
+impl From<i64> for DiagnosticCategory {
+ fn from(value: i64) -> Self {
+ match value {
+ 0 => DiagnosticCategory::Log,
+ 1 => DiagnosticCategory::Debug,
+ 2 => DiagnosticCategory::Info,
+ 3 => DiagnosticCategory::Error,
+ 4 => DiagnosticCategory::Warning,
+ 5 => DiagnosticCategory::Suggestion,
+ _ => panic!("Unknown value: {}", value),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::ansi::strip_ansi_codes;
+
+ fn diagnostic1() -> Diagnostic {
+ Diagnostic {
+ items: vec![
+ DiagnosticItem {
+ message: "Type '(o: T) => { v: any; f: (x: B) => string; }[]' is not assignable to type '(r: B) => Value<B>[]'.".to_string(),
+ message_chain: Some(Box::new(DiagnosticMessageChain {
+ message: "Type '(o: T) => { v: any; f: (x: B) => string; }[]' is not assignable to type '(r: B) => Value<B>[]'.".to_string(),
+ code: 2322,
+ category: DiagnosticCategory::Error,
+ next: Some(Box::new(DiagnosticMessageChain {
+ message: "Types of parameters 'o' and 'r' are incompatible.".to_string(),
+ code: 2328,
+ category: DiagnosticCategory::Error,
+ next: Some(Box::new(DiagnosticMessageChain {
+ message: "Type 'B' is not assignable to type 'T'.".to_string(),
+ code: 2322,
+ category: DiagnosticCategory::Error,
+ next: None,
+ })),
+ })),
+ })),
+ code: 2322,
+ category: DiagnosticCategory::Error,
+ start_position: Some(267),
+ end_position: Some(273),
+ source_line: Some(" values: o => [".to_string()),
+ line_number: Some(18),
+ script_resource_name: Some("deno/tests/complex_diagnostics.ts".to_string()),
+ start_column: Some(2),
+ end_column: Some(8),
+ related_information: Some(vec![
+ DiagnosticItem {
+ message: "The expected type comes from property 'values' which is declared here on type 'SettingsInterface<B>'".to_string(),
+ message_chain: None,
+ related_information: None,
+ code: 6500,
+ source_line: Some(" values?: (r: T) => Array<Value<T>>;".to_string()),
+ script_resource_name: Some("deno/tests/complex_diagnostics.ts".to_string()),
+ line_number: Some(6),
+ start_position: Some(94),
+ end_position: Some(100),
+ category: DiagnosticCategory::Info,
+ start_column: Some(2),
+ end_column: Some(8),
+ }
+ ])
+ }
+ ]
+ }
+ }
+
+ fn diagnostic2() -> Diagnostic {
+ Diagnostic {
+ items: vec![
+ DiagnosticItem {
+ message: "Example 1".to_string(),
+ message_chain: None,
+ code: 2322,
+ category: DiagnosticCategory::Error,
+ start_position: Some(267),
+ end_position: Some(273),
+ source_line: Some(" values: o => [".to_string()),
+ line_number: Some(18),
+ script_resource_name: Some(
+ "deno/tests/complex_diagnostics.ts".to_string(),
+ ),
+ start_column: Some(2),
+ end_column: Some(8),
+ related_information: None,
+ },
+ DiagnosticItem {
+ message: "Example 2".to_string(),
+ message_chain: None,
+ code: 2000,
+ category: DiagnosticCategory::Error,
+ start_position: Some(2),
+ end_position: Some(2),
+ source_line: Some(" values: undefined,".to_string()),
+ line_number: Some(128),
+ script_resource_name: Some("/foo/bar.ts".to_string()),
+ start_column: Some(2),
+ end_column: Some(8),
+ related_information: None,
+ },
+ ],
+ }
+ }
+
+ #[test]
+ fn from_json() {
+ let v = serde_json::from_str::<serde_json::Value>(
+ &r#"{
+ "items": [
+ {
+ "message": "Type '(o: T) => { v: any; f: (x: B) => string; }[]' is not assignable to type '(r: B) => Value<B>[]'.",
+ "messageChain": {
+ "message": "Type '(o: T) => { v: any; f: (x: B) => string; }[]' is not assignable to type '(r: B) => Value<B>[]'.",
+ "code": 2322,
+ "category": 3,
+ "next": {
+ "message": "Types of parameters 'o' and 'r' are incompatible.",
+ "code": 2328,
+ "category": 3,
+ "next": {
+ "message": "Type 'B' is not assignable to type 'T'.",
+ "code": 2322,
+ "category": 3
+ }
+ }
+ },
+ "code": 2322,
+ "category": 3,
+ "startPosition": 235,
+ "endPosition": 241,
+ "sourceLine": " values: o => [",
+ "lineNumber": 18,
+ "scriptResourceName": "/deno/tests/complex_diagnostics.ts",
+ "startColumn": 2,
+ "endColumn": 8,
+ "relatedInformation": [
+ {
+ "message": "The expected type comes from property 'values' which is declared here on type 'C<B>'",
+ "code": 6500,
+ "category": 2,
+ "startPosition": 78,
+ "endPosition": 84,
+ "sourceLine": " values?: (r: T) => Array<Value<T>>;",
+ "lineNumber": 6,
+ "scriptResourceName": "/deno/tests/complex_diagnostics.ts",
+ "startColumn": 2,
+ "endColumn": 8
+ }
+ ]
+ },
+ {
+ "message": "Property 't' does not exist on type 'T'.",
+ "code": 2339,
+ "category": 3,
+ "startPosition": 267,
+ "endPosition": 268,
+ "sourceLine": " v: o.t,",
+ "lineNumber": 20,
+ "scriptResourceName": "/deno/tests/complex_diagnostics.ts",
+ "startColumn": 11,
+ "endColumn": 12
+ }
+ ]
+ }"#,
+ ).unwrap();
+ let r = Diagnostic::from_json_value(&v);
+ let expected = Some(Diagnostic {
+ items: vec![
+ DiagnosticItem {
+ message: "Type '(o: T) => { v: any; f: (x: B) => string; }[]' is not assignable to type '(r: B) => Value<B>[]'.".to_string(),
+ message_chain: Some(Box::new(DiagnosticMessageChain {
+ message: "Type '(o: T) => { v: any; f: (x: B) => string; }[]' is not assignable to type '(r: B) => Value<B>[]'.".to_string(),
+ code: 2322,
+ category: DiagnosticCategory::Error,
+ next: Some(Box::new(DiagnosticMessageChain {
+ message: "Types of parameters 'o' and 'r' are incompatible.".to_string(),
+ code: 2328,
+ category: DiagnosticCategory::Error,
+ next: Some(Box::new(DiagnosticMessageChain {
+ message: "Type 'B' is not assignable to type 'T'.".to_string(),
+ code: 2322,
+ category: DiagnosticCategory::Error,
+ next: None,
+ })),
+ })),
+ })),
+ related_information: Some(vec![
+ DiagnosticItem {
+ message: "The expected type comes from property 'values' which is declared here on type 'C<B>'".to_string(),
+ message_chain: None,
+ related_information: None,
+ source_line: Some(" values?: (r: T) => Array<Value<T>>;".to_string()),
+ line_number: Some(6),
+ script_resource_name: Some("/deno/tests/complex_diagnostics.ts".to_string()),
+ start_position: Some(78),
+ end_position: Some(84),
+ category: DiagnosticCategory::Info,
+ code: 6500,
+ start_column: Some(2),
+ end_column: Some(8),
+ }
+ ]),
+ source_line: Some(" values: o => [".to_string()),
+ line_number: Some(18),
+ script_resource_name: Some("/deno/tests/complex_diagnostics.ts".to_string()),
+ start_position: Some(235),
+ end_position: Some(241),
+ category: DiagnosticCategory::Error,
+ code: 2322,
+ start_column: Some(2),
+ end_column: Some(8),
+ },
+ DiagnosticItem {
+ message: "Property 't' does not exist on type 'T'.".to_string(),
+ message_chain: None,
+ related_information: None,
+ source_line: Some(" v: o.t,".to_string()),
+ line_number: Some(20),
+ script_resource_name: Some("/deno/tests/complex_diagnostics.ts".to_string()),
+ start_position: Some(267),
+ end_position: Some(268),
+ category: DiagnosticCategory::Error,
+ code: 2339,
+ start_column: Some(11),
+ end_column: Some(12),
+ },
+ ],
+ });
+ assert_eq!(expected, r);
+ }
+
+ #[test]
+ fn from_emit_result() {
+ let r = Diagnostic::from_emit_result(
+ &r#"{
+ "emitSkipped": false,
+ "diagnostics": {
+ "items": [
+ {
+ "message": "foo bar",
+ "code": 9999,
+ "category": 3
+ }
+ ]
+ }
+ }"#,
+ );
+ let expected = Some(Diagnostic {
+ items: vec![DiagnosticItem {
+ message: "foo bar".to_string(),
+ message_chain: None,
+ related_information: None,
+ source_line: None,
+ line_number: None,
+ script_resource_name: None,
+ start_position: None,
+ end_position: None,
+ category: DiagnosticCategory::Error,
+ code: 9999,
+ start_column: None,
+ end_column: None,
+ }],
+ });
+ assert_eq!(expected, r);
+ }
+
+ #[test]
+ fn from_emit_result_none() {
+ let r = &r#"{"emitSkipped":false}"#;
+ assert!(Diagnostic::from_emit_result(r).is_none());
+ }
+
+ #[test]
+ fn diagnostic_to_string1() {
+ let d = diagnostic1();
+ let expected = "deno/tests/complex_diagnostics.ts:19:3 - error TS2322: Type \'(o: T) => { v: any; f: (x: B) => string; }[]\' is not assignable to type \'(r: B) => Value<B>[]\'.\n Types of parameters \'o\' and \'r\' are incompatible.\n Type \'B\' is not assignable to type \'T\'.\n\n19 values: o => [\n ~~~~~~\n\n deno/tests/complex_diagnostics.ts:7:3 \n\n 7 values?: (r: T) => Array<Value<T>>;\n ~~~~~~\n The expected type comes from property \'values\' which is declared here on type \'SettingsInterface<B>\'\n";
+ assert_eq!(expected, strip_ansi_codes(&d.to_string()));
+ }
+
+ #[test]
+ fn diagnostic_to_string2() {
+ let d = diagnostic2();
+ let expected = "deno/tests/complex_diagnostics.ts:19:3 - error TS2322: Example 1\n\n19 values: o => [\n ~~~~~~\n\n/foo/bar.ts:129:3 - error TS2000: Example 2\n\n129 values: undefined,\n ~~~~~~\n\n\nFound 2 errors.\n";
+ assert_eq!(expected, strip_ansi_codes(&d.to_string()));
+ }
+}
diff --git a/cli/main.rs b/cli/main.rs
index 5d70805ae..953e01943 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -16,6 +16,7 @@ extern crate rand;
mod ansi;
pub mod compiler;
pub mod deno_dir;
+pub mod diagnostics;
mod dispatch_minimal;
pub mod errors;
pub mod flags;
diff --git a/cli/worker.rs b/cli/worker.rs
index 59eecda6f..c08a43385 100644
--- a/cli/worker.rs
+++ b/cli/worker.rs
@@ -4,7 +4,6 @@ use crate::compiler::ModuleMetaData;
use crate::errors::DenoError;
use crate::errors::RustOrJsError;
use crate::js_errors;
-use crate::js_errors::JSErrorColor;
use crate::msg;
use crate::state::ThreadSafeState;
use crate::tokio_util;
@@ -233,7 +232,7 @@ fn fetch_module_meta_data_and_maybe_compile_async(
compile_async(state_.clone(), &specifier, &referrer, &out)
.map_err(|e| {
debug!("compiler error exiting!");
- eprintln!("{}", JSErrorColor(&e).to_string());
+ eprintln!("\n{}", e.to_string());
std::process::exit(1);
}).and_then(move |out| {
debug!(">>>>> compile_sync END");