mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 09:31:22 -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/custom_event.ts",
|
||||
"../js/deno.ts",
|
||||
"../js/diagnostics.ts",
|
||||
"../js/dir.ts",
|
||||
"../js/dispatch.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.
|
||||
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() {
|
||||
|
|
|
@ -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,
|
||||
|
|
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;
|
||||
pub mod compiler;
|
||||
pub mod deno_dir;
|
||||
pub mod diagnostics;
|
||||
mod dispatch_minimal;
|
||||
pub mod errors;
|
||||
pub mod flags;
|
||||
|
|
|
@ -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");
|
||||
|
|
126
js/compiler.ts
126
js/compiler.ts
|
@ -1,6 +1,7 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
import * as msg from "gen/cli/msg_generated";
|
||||
import { core } from "./core";
|
||||
import { Diagnostic, fromTypeScriptDiagnostic } from "./diagnostics";
|
||||
import * as flatbuffers from "./flatbuffers";
|
||||
import { sendSync } from "./dispatch";
|
||||
import { TextDecoder } from "./text_encoding";
|
||||
|
@ -37,6 +38,11 @@ interface CompilerReq {
|
|||
config?: string;
|
||||
}
|
||||
|
||||
interface ConfigureResponse {
|
||||
ignoredOptions?: string[];
|
||||
diagnostics?: ts.Diagnostic[];
|
||||
}
|
||||
|
||||
/** Options that either do nothing in Deno, or would cause undesired behavior
|
||||
* if modified. */
|
||||
const ignoredCompilerOptions: ReadonlyArray<string> = [
|
||||
|
@ -105,6 +111,11 @@ interface ModuleMetaData {
|
|||
sourceCode: string | undefined;
|
||||
}
|
||||
|
||||
interface EmitResult {
|
||||
emitSkipped: boolean;
|
||||
diagnostics?: Diagnostic;
|
||||
}
|
||||
|
||||
function fetchModuleMetaData(
|
||||
specifier: string,
|
||||
referrer: string
|
||||
|
@ -193,22 +204,19 @@ class Host implements ts.CompilerHost {
|
|||
* compiler's configuration options. The method returns an array of compiler
|
||||
* options which were ignored, or `undefined`.
|
||||
*/
|
||||
configure(path: string, configurationText: string): string[] | undefined {
|
||||
configure(path: string, configurationText: string): ConfigureResponse {
|
||||
util.log("compile.configure", path);
|
||||
const { config, error } = ts.parseConfigFileTextToJson(
|
||||
path,
|
||||
configurationText
|
||||
);
|
||||
if (error) {
|
||||
this._logDiagnostics([error]);
|
||||
return { diagnostics: [error] };
|
||||
}
|
||||
const { options, errors } = ts.convertCompilerOptionsFromJson(
|
||||
config.compilerOptions,
|
||||
cwd()
|
||||
);
|
||||
if (errors.length) {
|
||||
this._logDiagnostics(errors);
|
||||
}
|
||||
const ignoredOptions: string[] = [];
|
||||
for (const key of Object.keys(options)) {
|
||||
if (
|
||||
|
@ -220,7 +228,10 @@ class Host implements ts.CompilerHost {
|
|||
}
|
||||
}
|
||||
Object.assign(this._options, options);
|
||||
return ignoredOptions.length ? ignoredOptions : undefined;
|
||||
return {
|
||||
ignoredOptions: ignoredOptions.length ? ignoredOptions : undefined,
|
||||
diagnostics: errors.length ? errors : undefined
|
||||
};
|
||||
}
|
||||
|
||||
getCompilationSettings(): ts.CompilerOptions {
|
||||
|
@ -228,19 +239,6 @@ class Host implements ts.CompilerHost {
|
|||
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 {
|
||||
return notImplemented();
|
||||
}
|
||||
|
@ -362,10 +360,17 @@ class Host implements ts.CompilerHost {
|
|||
window.compilerMain = function compilerMain(): void {
|
||||
// workerMain should have already been called since a compiler is a worker.
|
||||
window.onmessage = ({ data }: { data: CompilerReq }): void => {
|
||||
let emitSkipped = true;
|
||||
let diagnostics: ts.Diagnostic[] | undefined;
|
||||
|
||||
const { rootNames, configPath, config } = data;
|
||||
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) {
|
||||
console.warn(
|
||||
yellow(`Unsupported compiler options in "${configPath}"\n`) +
|
||||
|
@ -377,51 +382,52 @@ window.compilerMain = function compilerMain(): void {
|
|||
}
|
||||
}
|
||||
|
||||
const options = host.getCompilationSettings();
|
||||
const program = ts.createProgram(rootNames, options, host);
|
||||
// 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 program = ts.createProgram(rootNames, options, host);
|
||||
|
||||
const preEmitDiagnostics = ts.getPreEmitDiagnostics(program).filter(
|
||||
({ code }): boolean => {
|
||||
// TS2691: An import path cannot end with a '.ts' extension. Consider
|
||||
// importing 'bad-module' instead.
|
||||
if (code === 2691) return false;
|
||||
// TS5009: Cannot find the common subdirectory path for the input files.
|
||||
if (code === 5009) return false;
|
||||
// TS5055: Cannot write file
|
||||
// 'http://localhost:4545/tests/subdir/mt_application_x_javascript.j4.js'
|
||||
// because it would overwrite input file.
|
||||
if (code === 5055) return false;
|
||||
// TypeScript is overly opinionated that only CommonJS modules kinds can
|
||||
// support JSON imports. Allegedly this was fixed in
|
||||
// Microsoft/TypeScript#26825 but that doesn't seem to be working here,
|
||||
// so we will ignore complaints about this compiler setting.
|
||||
if (code === 5070) return false;
|
||||
return true;
|
||||
diagnostics = ts.getPreEmitDiagnostics(program).filter(
|
||||
({ code }): boolean => {
|
||||
// TS2691: An import path cannot end with a '.ts' extension. Consider
|
||||
// importing 'bad-module' instead.
|
||||
if (code === 2691) return false;
|
||||
// TS5009: Cannot find the common subdirectory path for the input files.
|
||||
if (code === 5009) return false;
|
||||
// TS5055: Cannot write file
|
||||
// 'http://localhost:4545/tests/subdir/mt_application_x_javascript.j4.js'
|
||||
// because it would overwrite input file.
|
||||
if (code === 5055) return false;
|
||||
// TypeScript is overly opinionated that only CommonJS modules kinds can
|
||||
// support JSON imports. Allegedly this was fixed in
|
||||
// Microsoft/TypeScript#26825 but that doesn't seem to be working here,
|
||||
// so we will ignore complaints about this compiler setting.
|
||||
if (code === 5070) return false;
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
// We will only proceed with the emit if there are no diagnostics.
|
||||
if (diagnostics && diagnostics.length === 0) {
|
||||
const emitResult = program.emit();
|
||||
emitSkipped = emitResult.emitSkipped;
|
||||
// emitResult.diagnostics is `readonly` in TS3.5+ and can't be assigned
|
||||
// without casting.
|
||||
diagnostics = emitResult.diagnostics as ts.Diagnostic[];
|
||||
}
|
||||
);
|
||||
if (preEmitDiagnostics.length > 0) {
|
||||
host._logDiagnostics(preEmitDiagnostics);
|
||||
// The above _logDiagnostics calls os.exit(). The return is here just for
|
||||
// clarity.
|
||||
return;
|
||||
}
|
||||
|
||||
const emitResult = program!.emit();
|
||||
const result: EmitResult = {
|
||||
emitSkipped,
|
||||
diagnostics: diagnostics.length
|
||||
? fromTypeScriptDiagnostic(diagnostics)
|
||||
: undefined
|
||||
};
|
||||
|
||||
// TODO(ry) Print diagnostics in Rust.
|
||||
// https://github.com/denoland/deno/pull/2310
|
||||
postMessage(result);
|
||||
|
||||
const { diagnostics } = emitResult;
|
||||
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.
|
||||
// The compiler isolate exits after a single message.
|
||||
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",
|
||||
"flatbuffers": "1.9.0",
|
||||
"magic-string": "0.25.2",
|
||||
"prettier": "1.16.4",
|
||||
"prettier": "1.17.1",
|
||||
"rollup": "1.4.1",
|
||||
"rollup-plugin-alias": "1.5.1",
|
||||
"rollup-plugin-analyzer": "3.0.0",
|
||||
|
|
|
@ -239,6 +239,7 @@ export default function makeConfig(commandOptions) {
|
|||
"parseConfigFileTextToJson",
|
||||
"version",
|
||||
"CompilerHost",
|
||||
"DiagnosticCategory",
|
||||
"Extension",
|
||||
"ModuleKind",
|
||||
"ScriptKind",
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
(async (): Promise<void> => {
|
||||
const {
|
||||
returnsHi,
|
||||
returnsFoo2,
|
||||
printHello3
|
||||
} = await import("./subdir/mod1.ts");
|
||||
const { returnsHi, returnsFoo2, printHello3 } = await import(
|
||||
"./subdir/mod1.ts"
|
||||
);
|
||||
|
||||
printHello3();
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
args: run --reload tests/error_003_typescript.ts
|
||||
check_stderr: true
|
||||
exit_code: 1
|
||||
output: tests/error_003_typescript.ts.out
|
||||
|
|
|
@ -1,2 +1,26 @@
|
|||
// console.log intentionally misspelled to trigger TypeScript error
|
||||
consol.log("hello world!");
|
||||
/* eslint-disable */
|
||||
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!");
|
||||
[WILDCARD]~~~~~~
|
||||
20 values: o => [
|
||||
~~~~~~
|
||||
|
||||
$asset$/lib.deno_runtime.d.ts[WILDCARD]
|
||||
[WILDCARD]declare const console: consoleTypes.Console;
|
||||
[WILDCARD]~~~~~~~
|
||||
[WILDCARD]'console' is declared here.
|
||||
[WILDCARD]/tests/error_003_typescript.ts:8:3
|
||||
|
||||
8 values?: (r: T) => Array<Value<T>>;
|
||||
~~~~~~
|
||||
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.
|
||||
# https://github.com/denoland/deno/issues/2436
|
||||
args: run tests/error_003_typescript.ts
|
||||
check_stderr: true
|
||||
exit_code: 1
|
||||
output: tests/error_003_typescript.ts.out
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 0761d3cee6dd43c38f676268b496a37527fc9bae
|
||||
Subproject commit 72a4202a0341516115a92aa18951eb3010fb75fa
|
Loading…
Add table
Reference in a new issue