diff options
Diffstat (limited to 'cli')
-rw-r--r-- | cli/BUILD.gn | 1 | ||||
-rw-r--r-- | cli/ansi.rs | 26 | ||||
-rw-r--r-- | cli/compiler.rs | 19 | ||||
-rw-r--r-- | cli/diagnostics.rs | 668 | ||||
-rw-r--r-- | cli/main.rs | 1 | ||||
-rw-r--r-- | cli/worker.rs | 3 |
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"); |