mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 17:34:47 -05:00
Handle compiler diagnostics in Rust (#2445)
This commit is contained in:
parent
60d4522641
commit
a71305b4fe
16 changed files with 1044 additions and 87 deletions
|
@ -74,6 +74,7 @@ ts_sources = [
|
||||||
"../js/core.ts",
|
"../js/core.ts",
|
||||||
"../js/custom_event.ts",
|
"../js/custom_event.ts",
|
||||||
"../js/deno.ts",
|
"../js/deno.ts",
|
||||||
|
"../js/diagnostics.ts",
|
||||||
"../js/dir.ts",
|
"../js/dir.ts",
|
||||||
"../js/dispatch.ts",
|
"../js/dispatch.ts",
|
||||||
"../js/dispatch_minimal.ts",
|
"../js/dispatch_minimal.ts",
|
||||||
|
|
26
cli/ansi.rs
26
cli/ansi.rs
|
@ -1,6 +1,8 @@
|
||||||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
// 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::Fixed;
|
||||||
use ansi_term::Color::Red;
|
use ansi_term::Color::Red;
|
||||||
|
use ansi_term::Color::White;
|
||||||
use ansi_term::Style;
|
use ansi_term::Style;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
@ -43,6 +45,14 @@ pub fn italic_bold(s: String) -> impl fmt::Display {
|
||||||
style.paint(s)
|
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 {
|
pub fn yellow(s: String) -> impl fmt::Display {
|
||||||
let mut style = Style::new();
|
let mut style = Style::new();
|
||||||
if use_color() {
|
if use_color() {
|
||||||
|
@ -61,6 +71,22 @@ pub fn cyan(s: String) -> impl fmt::Display {
|
||||||
style.paint(s)
|
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 {
|
pub fn bold(s: String) -> impl fmt::Display {
|
||||||
let mut style = Style::new();
|
let mut style = Style::new();
|
||||||
if use_color() {
|
if use_color() {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
|
use crate::diagnostics::Diagnostic;
|
||||||
use crate::msg;
|
use crate::msg;
|
||||||
use crate::resources;
|
use crate::resources;
|
||||||
use crate::startup_data;
|
use crate::startup_data;
|
||||||
|
@ -7,7 +8,6 @@ use crate::tokio_util;
|
||||||
use crate::worker::Worker;
|
use crate::worker::Worker;
|
||||||
use deno::js_check;
|
use deno::js_check;
|
||||||
use deno::Buf;
|
use deno::Buf;
|
||||||
use deno::JSError;
|
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
@ -87,7 +87,7 @@ pub fn compile_async(
|
||||||
specifier: &str,
|
specifier: &str,
|
||||||
referrer: &str,
|
referrer: &str,
|
||||||
module_meta_data: &ModuleMetaData,
|
module_meta_data: &ModuleMetaData,
|
||||||
) -> impl Future<Item = ModuleMetaData, Error = JSError> {
|
) -> impl Future<Item = ModuleMetaData, Error = Diagnostic> {
|
||||||
debug!(
|
debug!(
|
||||||
"Running rust part of compile_sync. specifier: {}, referrer: {}",
|
"Running rust part of compile_sync. specifier: {}, referrer: {}",
|
||||||
&specifier, &referrer
|
&specifier, &referrer
|
||||||
|
@ -136,14 +136,15 @@ pub fn compile_async(
|
||||||
first_msg_fut
|
first_msg_fut
|
||||||
.map_err(|_| panic!("not handled"))
|
.map_err(|_| panic!("not handled"))
|
||||||
.and_then(move |maybe_msg: Option<Buf>| {
|
.and_then(move |maybe_msg: Option<Buf>| {
|
||||||
let _res_msg = maybe_msg.unwrap();
|
|
||||||
|
|
||||||
debug!("Received message from worker");
|
debug!("Received message from worker");
|
||||||
|
|
||||||
// TODO res is EmitResult, use serde_derive to parse it. Errors from the
|
if let Some(msg) = maybe_msg {
|
||||||
// worker or Diagnostics should be somehow forwarded to the caller!
|
let json_str = std::str::from_utf8(&msg).unwrap();
|
||||||
// Currently they are handled inside compiler.ts with os.exit(1) and above
|
debug!("Message: {}", json_str);
|
||||||
// with std::process::exit(1). This bad.
|
if let Some(diagnostics) = Diagnostic::from_emit_result(json_str) {
|
||||||
|
return Err(diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let r = state.dir.fetch_module_meta_data(
|
let r = state.dir.fetch_module_meta_data(
|
||||||
&module_meta_data_.module_name,
|
&module_meta_data_.module_name,
|
||||||
|
@ -169,7 +170,7 @@ pub fn compile_sync(
|
||||||
specifier: &str,
|
specifier: &str,
|
||||||
referrer: &str,
|
referrer: &str,
|
||||||
module_meta_data: &ModuleMetaData,
|
module_meta_data: &ModuleMetaData,
|
||||||
) -> Result<ModuleMetaData, JSError> {
|
) -> Result<ModuleMetaData, Diagnostic> {
|
||||||
tokio_util::block_on(compile_async(
|
tokio_util::block_on(compile_async(
|
||||||
state,
|
state,
|
||||||
specifier,
|
specifier,
|
||||||
|
|
668
cli/diagnostics.rs
Normal file
668
cli/diagnostics.rs
Normal file
|
@ -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()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ extern crate rand;
|
||||||
mod ansi;
|
mod ansi;
|
||||||
pub mod compiler;
|
pub mod compiler;
|
||||||
pub mod deno_dir;
|
pub mod deno_dir;
|
||||||
|
pub mod diagnostics;
|
||||||
mod dispatch_minimal;
|
mod dispatch_minimal;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod flags;
|
pub mod flags;
|
||||||
|
|
|
@ -4,7 +4,6 @@ use crate::compiler::ModuleMetaData;
|
||||||
use crate::errors::DenoError;
|
use crate::errors::DenoError;
|
||||||
use crate::errors::RustOrJsError;
|
use crate::errors::RustOrJsError;
|
||||||
use crate::js_errors;
|
use crate::js_errors;
|
||||||
use crate::js_errors::JSErrorColor;
|
|
||||||
use crate::msg;
|
use crate::msg;
|
||||||
use crate::state::ThreadSafeState;
|
use crate::state::ThreadSafeState;
|
||||||
use crate::tokio_util;
|
use crate::tokio_util;
|
||||||
|
@ -233,7 +232,7 @@ fn fetch_module_meta_data_and_maybe_compile_async(
|
||||||
compile_async(state_.clone(), &specifier, &referrer, &out)
|
compile_async(state_.clone(), &specifier, &referrer, &out)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
debug!("compiler error exiting!");
|
debug!("compiler error exiting!");
|
||||||
eprintln!("{}", JSErrorColor(&e).to_string());
|
eprintln!("\n{}", e.to_string());
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}).and_then(move |out| {
|
}).and_then(move |out| {
|
||||||
debug!(">>>>> compile_sync END");
|
debug!(">>>>> compile_sync END");
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
import * as msg from "gen/cli/msg_generated";
|
import * as msg from "gen/cli/msg_generated";
|
||||||
import { core } from "./core";
|
import { core } from "./core";
|
||||||
|
import { Diagnostic, fromTypeScriptDiagnostic } from "./diagnostics";
|
||||||
import * as flatbuffers from "./flatbuffers";
|
import * as flatbuffers from "./flatbuffers";
|
||||||
import { sendSync } from "./dispatch";
|
import { sendSync } from "./dispatch";
|
||||||
import { TextDecoder } from "./text_encoding";
|
import { TextDecoder } from "./text_encoding";
|
||||||
|
@ -37,6 +38,11 @@ interface CompilerReq {
|
||||||
config?: string;
|
config?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ConfigureResponse {
|
||||||
|
ignoredOptions?: string[];
|
||||||
|
diagnostics?: ts.Diagnostic[];
|
||||||
|
}
|
||||||
|
|
||||||
/** Options that either do nothing in Deno, or would cause undesired behavior
|
/** Options that either do nothing in Deno, or would cause undesired behavior
|
||||||
* if modified. */
|
* if modified. */
|
||||||
const ignoredCompilerOptions: ReadonlyArray<string> = [
|
const ignoredCompilerOptions: ReadonlyArray<string> = [
|
||||||
|
@ -105,6 +111,11 @@ interface ModuleMetaData {
|
||||||
sourceCode: string | undefined;
|
sourceCode: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EmitResult {
|
||||||
|
emitSkipped: boolean;
|
||||||
|
diagnostics?: Diagnostic;
|
||||||
|
}
|
||||||
|
|
||||||
function fetchModuleMetaData(
|
function fetchModuleMetaData(
|
||||||
specifier: string,
|
specifier: string,
|
||||||
referrer: string
|
referrer: string
|
||||||
|
@ -193,22 +204,19 @@ class Host implements ts.CompilerHost {
|
||||||
* compiler's configuration options. The method returns an array of compiler
|
* compiler's configuration options. The method returns an array of compiler
|
||||||
* options which were ignored, or `undefined`.
|
* options which were ignored, or `undefined`.
|
||||||
*/
|
*/
|
||||||
configure(path: string, configurationText: string): string[] | undefined {
|
configure(path: string, configurationText: string): ConfigureResponse {
|
||||||
util.log("compile.configure", path);
|
util.log("compile.configure", path);
|
||||||
const { config, error } = ts.parseConfigFileTextToJson(
|
const { config, error } = ts.parseConfigFileTextToJson(
|
||||||
path,
|
path,
|
||||||
configurationText
|
configurationText
|
||||||
);
|
);
|
||||||
if (error) {
|
if (error) {
|
||||||
this._logDiagnostics([error]);
|
return { diagnostics: [error] };
|
||||||
}
|
}
|
||||||
const { options, errors } = ts.convertCompilerOptionsFromJson(
|
const { options, errors } = ts.convertCompilerOptionsFromJson(
|
||||||
config.compilerOptions,
|
config.compilerOptions,
|
||||||
cwd()
|
cwd()
|
||||||
);
|
);
|
||||||
if (errors.length) {
|
|
||||||
this._logDiagnostics(errors);
|
|
||||||
}
|
|
||||||
const ignoredOptions: string[] = [];
|
const ignoredOptions: string[] = [];
|
||||||
for (const key of Object.keys(options)) {
|
for (const key of Object.keys(options)) {
|
||||||
if (
|
if (
|
||||||
|
@ -220,7 +228,10 @@ class Host implements ts.CompilerHost {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Object.assign(this._options, options);
|
Object.assign(this._options, options);
|
||||||
return ignoredOptions.length ? ignoredOptions : undefined;
|
return {
|
||||||
|
ignoredOptions: ignoredOptions.length ? ignoredOptions : undefined,
|
||||||
|
diagnostics: errors.length ? errors : undefined
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getCompilationSettings(): ts.CompilerOptions {
|
getCompilationSettings(): ts.CompilerOptions {
|
||||||
|
@ -228,19 +239,6 @@ class Host implements ts.CompilerHost {
|
||||||
return this._options;
|
return this._options;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Log TypeScript diagnostics to the console and exit */
|
|
||||||
_logDiagnostics(diagnostics: ReadonlyArray<ts.Diagnostic>): never {
|
|
||||||
const errMsg = os.noColor
|
|
||||||
? ts.formatDiagnostics(diagnostics, this)
|
|
||||||
: ts.formatDiagnosticsWithColorAndContext(diagnostics, this);
|
|
||||||
|
|
||||||
console.log(errMsg);
|
|
||||||
// TODO The compiler isolate shouldn't call os.exit(). (In fact, it
|
|
||||||
// shouldn't even have access to call that op.) Errors should be forwarded
|
|
||||||
// to to the caller and the caller exit.
|
|
||||||
return os.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
fileExists(_fileName: string): boolean {
|
fileExists(_fileName: string): boolean {
|
||||||
return notImplemented();
|
return notImplemented();
|
||||||
}
|
}
|
||||||
|
@ -362,10 +360,17 @@ class Host implements ts.CompilerHost {
|
||||||
window.compilerMain = function compilerMain(): void {
|
window.compilerMain = function compilerMain(): void {
|
||||||
// workerMain should have already been called since a compiler is a worker.
|
// workerMain should have already been called since a compiler is a worker.
|
||||||
window.onmessage = ({ data }: { data: CompilerReq }): void => {
|
window.onmessage = ({ data }: { data: CompilerReq }): void => {
|
||||||
|
let emitSkipped = true;
|
||||||
|
let diagnostics: ts.Diagnostic[] | undefined;
|
||||||
|
|
||||||
const { rootNames, configPath, config } = data;
|
const { rootNames, configPath, config } = data;
|
||||||
const host = new Host();
|
const host = new Host();
|
||||||
if (config && config.length) {
|
|
||||||
const ignoredOptions = host.configure(configPath!, config);
|
// if there is a configuration supplied, we need to parse that
|
||||||
|
if (config && config.length && configPath) {
|
||||||
|
const configResult = host.configure(configPath, config);
|
||||||
|
const ignoredOptions = configResult.ignoredOptions;
|
||||||
|
diagnostics = configResult.diagnostics;
|
||||||
if (ignoredOptions) {
|
if (ignoredOptions) {
|
||||||
console.warn(
|
console.warn(
|
||||||
yellow(`Unsupported compiler options in "${configPath}"\n`) +
|
yellow(`Unsupported compiler options in "${configPath}"\n`) +
|
||||||
|
@ -377,10 +382,13 @@ window.compilerMain = function compilerMain(): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if there was a configuration and no diagnostics with it, we will continue
|
||||||
|
// to generate the program and possibly emit it.
|
||||||
|
if (!diagnostics || (diagnostics && diagnostics.length === 0)) {
|
||||||
const options = host.getCompilationSettings();
|
const options = host.getCompilationSettings();
|
||||||
const program = ts.createProgram(rootNames, options, host);
|
const program = ts.createProgram(rootNames, options, host);
|
||||||
|
|
||||||
const preEmitDiagnostics = ts.getPreEmitDiagnostics(program).filter(
|
diagnostics = ts.getPreEmitDiagnostics(program).filter(
|
||||||
({ code }): boolean => {
|
({ code }): boolean => {
|
||||||
// TS2691: An import path cannot end with a '.ts' extension. Consider
|
// TS2691: An import path cannot end with a '.ts' extension. Consider
|
||||||
// importing 'bad-module' instead.
|
// importing 'bad-module' instead.
|
||||||
|
@ -399,29 +407,27 @@ window.compilerMain = function compilerMain(): void {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (preEmitDiagnostics.length > 0) {
|
|
||||||
host._logDiagnostics(preEmitDiagnostics);
|
// We will only proceed with the emit if there are no diagnostics.
|
||||||
// The above _logDiagnostics calls os.exit(). The return is here just for
|
if (diagnostics && diagnostics.length === 0) {
|
||||||
// clarity.
|
const emitResult = program.emit();
|
||||||
return;
|
emitSkipped = emitResult.emitSkipped;
|
||||||
|
// emitResult.diagnostics is `readonly` in TS3.5+ and can't be assigned
|
||||||
|
// without casting.
|
||||||
|
diagnostics = emitResult.diagnostics as ts.Diagnostic[];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const emitResult = program!.emit();
|
const result: EmitResult = {
|
||||||
|
emitSkipped,
|
||||||
|
diagnostics: diagnostics.length
|
||||||
|
? fromTypeScriptDiagnostic(diagnostics)
|
||||||
|
: undefined
|
||||||
|
};
|
||||||
|
|
||||||
// TODO(ry) Print diagnostics in Rust.
|
postMessage(result);
|
||||||
// https://github.com/denoland/deno/pull/2310
|
|
||||||
|
|
||||||
const { diagnostics } = emitResult;
|
// The compiler isolate exits after a single message.
|
||||||
if (diagnostics.length > 0) {
|
|
||||||
host._logDiagnostics(diagnostics);
|
|
||||||
// The above _logDiagnostics calls os.exit(). The return is here just for
|
|
||||||
// clarity.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
postMessage(emitResult);
|
|
||||||
|
|
||||||
// The compiler isolate exits after a single messsage.
|
|
||||||
workerClose();
|
workerClose();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
218
js/diagnostics.ts
Normal file
218
js/diagnostics.ts
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
// Diagnostic provides an abstraction for advice/errors received from a
|
||||||
|
// compiler, which is strongly influenced by the format of TypeScript
|
||||||
|
// diagnostics.
|
||||||
|
|
||||||
|
import * as ts from "typescript";
|
||||||
|
|
||||||
|
/** The log category for a diagnostic message */
|
||||||
|
export enum DiagnosticCategory {
|
||||||
|
Log = 0,
|
||||||
|
Debug = 1,
|
||||||
|
Info = 2,
|
||||||
|
Error = 3,
|
||||||
|
Warning = 4,
|
||||||
|
Suggestion = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DiagnosticMessageChain {
|
||||||
|
message: string;
|
||||||
|
category: DiagnosticCategory;
|
||||||
|
code: number;
|
||||||
|
next?: DiagnosticMessageChain;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DiagnosticItem {
|
||||||
|
/** A string message summarizing the diagnostic. */
|
||||||
|
message: string;
|
||||||
|
|
||||||
|
/** An ordered array of further diagnostics. */
|
||||||
|
messageChain?: DiagnosticMessageChain;
|
||||||
|
|
||||||
|
/** Information related to the diagnostic. This is present when there is a
|
||||||
|
* suggestion or other additional diagnostic information */
|
||||||
|
relatedInformation?: DiagnosticItem[];
|
||||||
|
|
||||||
|
/** The text of the source line related to the diagnostic */
|
||||||
|
sourceLine?: string;
|
||||||
|
|
||||||
|
/** The line number that is related to the diagnostic */
|
||||||
|
lineNumber?: number;
|
||||||
|
|
||||||
|
/** The name of the script resource related to the diagnostic */
|
||||||
|
scriptResourceName?: string;
|
||||||
|
|
||||||
|
/** The start position related to the diagnostic */
|
||||||
|
startPosition?: number;
|
||||||
|
|
||||||
|
/** The end position related to the diagnostic */
|
||||||
|
endPosition?: number;
|
||||||
|
|
||||||
|
/** The category of the diagnostic */
|
||||||
|
category: DiagnosticCategory;
|
||||||
|
|
||||||
|
/** A number identifier */
|
||||||
|
code: number;
|
||||||
|
|
||||||
|
/** The the start column of the sourceLine related to the diagnostic */
|
||||||
|
startColumn?: number;
|
||||||
|
|
||||||
|
/** The end column of the sourceLine related to the diagnostic */
|
||||||
|
endColumn?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Diagnostic {
|
||||||
|
/** An array of diagnostic items. */
|
||||||
|
items: DiagnosticItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SourceInformation {
|
||||||
|
sourceLine: string;
|
||||||
|
lineNumber: number;
|
||||||
|
scriptResourceName: string;
|
||||||
|
startColumn: number;
|
||||||
|
endColumn: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromDiagnosticCategory(
|
||||||
|
category: ts.DiagnosticCategory
|
||||||
|
): DiagnosticCategory {
|
||||||
|
switch (category) {
|
||||||
|
case ts.DiagnosticCategory.Error:
|
||||||
|
return DiagnosticCategory.Error;
|
||||||
|
case ts.DiagnosticCategory.Message:
|
||||||
|
return DiagnosticCategory.Info;
|
||||||
|
case ts.DiagnosticCategory.Suggestion:
|
||||||
|
return DiagnosticCategory.Suggestion;
|
||||||
|
case ts.DiagnosticCategory.Warning:
|
||||||
|
return DiagnosticCategory.Warning;
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unexpected DiagnosticCategory: "${category}"/"${
|
||||||
|
ts.DiagnosticCategory[category]
|
||||||
|
}"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceInformation(
|
||||||
|
sourceFile: ts.SourceFile,
|
||||||
|
start: number,
|
||||||
|
length: number
|
||||||
|
): SourceInformation {
|
||||||
|
const scriptResourceName = sourceFile.fileName;
|
||||||
|
const {
|
||||||
|
line: lineNumber,
|
||||||
|
character: startColumn
|
||||||
|
} = sourceFile.getLineAndCharacterOfPosition(start);
|
||||||
|
const endPosition = sourceFile.getLineAndCharacterOfPosition(start + length);
|
||||||
|
const endColumn =
|
||||||
|
lineNumber === endPosition.line ? endPosition.character : startColumn;
|
||||||
|
const lastLineInFile = sourceFile.getLineAndCharacterOfPosition(
|
||||||
|
sourceFile.text.length
|
||||||
|
).line;
|
||||||
|
const lineStart = sourceFile.getPositionOfLineAndCharacter(lineNumber, 0);
|
||||||
|
const lineEnd =
|
||||||
|
lineNumber < lastLineInFile
|
||||||
|
? sourceFile.getPositionOfLineAndCharacter(lineNumber + 1, 0)
|
||||||
|
: sourceFile.text.length;
|
||||||
|
const sourceLine = sourceFile.text
|
||||||
|
.slice(lineStart, lineEnd)
|
||||||
|
.replace(/\s+$/g, "")
|
||||||
|
.replace("\t", " ");
|
||||||
|
return {
|
||||||
|
sourceLine,
|
||||||
|
lineNumber,
|
||||||
|
scriptResourceName,
|
||||||
|
startColumn,
|
||||||
|
endColumn
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Converts a TypeScript diagnostic message chain to a Deno one. */
|
||||||
|
function fromDiagnosticMessageChain(
|
||||||
|
messageChain: ts.DiagnosticMessageChain | undefined
|
||||||
|
): DiagnosticMessageChain | undefined {
|
||||||
|
if (!messageChain) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { messageText: message, code, category, next } = messageChain;
|
||||||
|
return {
|
||||||
|
message,
|
||||||
|
code,
|
||||||
|
category: fromDiagnosticCategory(category),
|
||||||
|
next: fromDiagnosticMessageChain(next)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parse out information from a TypeScript diagnostic structure. */
|
||||||
|
function parseDiagnostic(
|
||||||
|
item: ts.Diagnostic | ts.DiagnosticRelatedInformation
|
||||||
|
): DiagnosticItem {
|
||||||
|
const {
|
||||||
|
messageText,
|
||||||
|
category: sourceCategory,
|
||||||
|
code,
|
||||||
|
file,
|
||||||
|
start: startPosition,
|
||||||
|
length
|
||||||
|
} = item;
|
||||||
|
const sourceInfo =
|
||||||
|
file && startPosition && length
|
||||||
|
? getSourceInformation(file, startPosition, length)
|
||||||
|
: undefined;
|
||||||
|
const endPosition =
|
||||||
|
startPosition && length ? startPosition + length : undefined;
|
||||||
|
const category = fromDiagnosticCategory(sourceCategory);
|
||||||
|
|
||||||
|
let message: string;
|
||||||
|
let messageChain: DiagnosticMessageChain | undefined;
|
||||||
|
if (typeof messageText === "string") {
|
||||||
|
message = messageText;
|
||||||
|
} else {
|
||||||
|
message = messageText.messageText;
|
||||||
|
messageChain = fromDiagnosticMessageChain(messageText);
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = {
|
||||||
|
message,
|
||||||
|
messageChain,
|
||||||
|
code,
|
||||||
|
category,
|
||||||
|
startPosition,
|
||||||
|
endPosition
|
||||||
|
};
|
||||||
|
|
||||||
|
return sourceInfo ? { ...base, ...sourceInfo } : base;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert a diagnostic related information array into a Deno diagnostic
|
||||||
|
* array. */
|
||||||
|
function parseRelatedInformation(
|
||||||
|
relatedInformation: readonly ts.DiagnosticRelatedInformation[]
|
||||||
|
): DiagnosticItem[] {
|
||||||
|
const result: DiagnosticItem[] = [];
|
||||||
|
for (const item of relatedInformation) {
|
||||||
|
result.push(parseDiagnostic(item));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert TypeScript diagnostics to Deno diagnostics. */
|
||||||
|
export function fromTypeScriptDiagnostic(
|
||||||
|
diagnostics: readonly ts.Diagnostic[]
|
||||||
|
): Diagnostic {
|
||||||
|
let items: DiagnosticItem[] = [];
|
||||||
|
for (const sourceDiagnostic of diagnostics) {
|
||||||
|
const item: DiagnosticItem = parseDiagnostic(sourceDiagnostic);
|
||||||
|
if (sourceDiagnostic.relatedInformation) {
|
||||||
|
item.relatedInformation = parseRelatedInformation(
|
||||||
|
sourceDiagnostic.relatedInformation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
return { items };
|
||||||
|
}
|
|
@ -12,7 +12,7 @@
|
||||||
"eslint-config-prettier": "4.1.0",
|
"eslint-config-prettier": "4.1.0",
|
||||||
"flatbuffers": "1.9.0",
|
"flatbuffers": "1.9.0",
|
||||||
"magic-string": "0.25.2",
|
"magic-string": "0.25.2",
|
||||||
"prettier": "1.16.4",
|
"prettier": "1.17.1",
|
||||||
"rollup": "1.4.1",
|
"rollup": "1.4.1",
|
||||||
"rollup-plugin-alias": "1.5.1",
|
"rollup-plugin-alias": "1.5.1",
|
||||||
"rollup-plugin-analyzer": "3.0.0",
|
"rollup-plugin-analyzer": "3.0.0",
|
||||||
|
|
|
@ -239,6 +239,7 @@ export default function makeConfig(commandOptions) {
|
||||||
"parseConfigFileTextToJson",
|
"parseConfigFileTextToJson",
|
||||||
"version",
|
"version",
|
||||||
"CompilerHost",
|
"CompilerHost",
|
||||||
|
"DiagnosticCategory",
|
||||||
"Extension",
|
"Extension",
|
||||||
"ModuleKind",
|
"ModuleKind",
|
||||||
"ScriptKind",
|
"ScriptKind",
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
(async (): Promise<void> => {
|
(async (): Promise<void> => {
|
||||||
const {
|
const { returnsHi, returnsFoo2, printHello3 } = await import(
|
||||||
returnsHi,
|
"./subdir/mod1.ts"
|
||||||
returnsFoo2,
|
);
|
||||||
printHello3
|
|
||||||
} = await import("./subdir/mod1.ts");
|
|
||||||
|
|
||||||
printHello3();
|
printHello3();
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
args: run --reload tests/error_003_typescript.ts
|
args: run --reload tests/error_003_typescript.ts
|
||||||
|
check_stderr: true
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
output: tests/error_003_typescript.ts.out
|
output: tests/error_003_typescript.ts.out
|
||||||
|
|
|
@ -1,2 +1,26 @@
|
||||||
// console.log intentionally misspelled to trigger TypeScript error
|
/* eslint-disable */
|
||||||
consol.log("hello world!");
|
interface Value<T> {
|
||||||
|
f?: (r: T) => any;
|
||||||
|
v?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface C<T> {
|
||||||
|
values?: (r: T) => Array<Value<T>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
class A<T> {
|
||||||
|
constructor(private e?: T, public s?: C<T>) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class B {
|
||||||
|
t = "foo";
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = new A(new B(), {
|
||||||
|
values: o => [
|
||||||
|
{
|
||||||
|
v: o.t,
|
||||||
|
f: x => "bar"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
|
@ -1,10 +1,22 @@
|
||||||
[WILDCARD]tests/error_003_typescript.ts[WILDCARD] - error TS2552: Cannot find name 'consol'. Did you mean 'console'?
|
[WILDCARD]/tests/error_003_typescript.ts:20:3 - error TS2322: Type '(o: T) => { v: any; f: (x: B) => string; }[]' is not assignable to type '(r: B) => Value<B>[]'.
|
||||||
|
Types of parameters 'o' and 'r' are incompatible.
|
||||||
|
Type 'B' is not assignable to type 'T'.
|
||||||
|
'B' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.
|
||||||
|
|
||||||
[WILDCARD] consol.log("hello world!");
|
20 values: o => [
|
||||||
[WILDCARD]~~~~~~
|
~~~~~~
|
||||||
|
|
||||||
$asset$/lib.deno_runtime.d.ts[WILDCARD]
|
[WILDCARD]/tests/error_003_typescript.ts:8:3
|
||||||
[WILDCARD]declare const console: consoleTypes.Console;
|
|
||||||
[WILDCARD]~~~~~~~
|
8 values?: (r: T) => Array<Value<T>>;
|
||||||
[WILDCARD]'console' is declared here.
|
~~~~~~
|
||||||
|
The expected type comes from property 'values' which is declared here on type 'C<B>'
|
||||||
|
|
||||||
|
[WILDCARD]/tests/error_003_typescript.ts:22:12 - error TS2339: Property 't' does not exist on type 'T'.
|
||||||
|
|
||||||
|
22 v: o.t,
|
||||||
|
^
|
||||||
|
|
||||||
|
|
||||||
|
Found 2 errors.
|
||||||
|
|
||||||
|
|
|
@ -3,5 +3,6 @@
|
||||||
# should result in the same output.
|
# should result in the same output.
|
||||||
# https://github.com/denoland/deno/issues/2436
|
# https://github.com/denoland/deno/issues/2436
|
||||||
args: run tests/error_003_typescript.ts
|
args: run tests/error_003_typescript.ts
|
||||||
|
check_stderr: true
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
output: tests/error_003_typescript.ts.out
|
output: tests/error_003_typescript.ts.out
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 0761d3cee6dd43c38f676268b496a37527fc9bae
|
Subproject commit 72a4202a0341516115a92aa18951eb3010fb75fa
|
Loading…
Add table
Reference in a new issue