Skip to main content
Module

x/deno/cli/diagnostics.rs

A modern runtime for JavaScript and TypeScript.
Go to Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.//! This module encodes TypeScript errors (diagnostics) into Rust structs and//! contains code for printing them to the console.
// TODO(ry) This module does a lot of JSON parsing manually. It should use// serde_json.
use crate::colors;use crate::fmt_errors::format_maybe_source_line;use crate::fmt_errors::format_maybe_source_name;use crate::fmt_errors::DisplayFormatter;use serde_json;use serde_json::value::Value;use std::error::Error;use std::fmt;
#[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(()) }}
impl Error for Diagnostic { fn description(&self) -> &str { &self.items[0].message }}
#[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<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) -> Option<Self> { let obj = v.as_object().unwrap();
// required attributes let message = obj .get("message") .and_then(|v| v.as_str().map(String::from))?; 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, };
Some(Self { message, message_chain, related_information, code, source_line, script_resource_name, line_number, start_position, end_position, category, start_column, end_column, }) }}
impl DisplayFormatter for DiagnosticItem { fn format_category_and_code(&self) -> String { let category = match self.category { DiagnosticCategory::Error => { format!("{}", colors::red_bold("error".to_string())) } DiagnosticCategory::Warning => "warn".to_string(), DiagnosticCategory::Debug => "debug".to_string(), DiagnosticCategory::Info => "info".to_string(), _ => "".to_string(), };
let code = colors::bold(format!(" TS{}", self.code.to_string())).to_string();
format!("{}{}: ", category, code) }
fn format_message(&self, level: usize) -> String { debug!("format_message"); if self.message_chain.is_none() { return format!("{:indent$}{}", "", self.message, indent = level); }
let mut s = self.message_chain.clone().unwrap().format_message(level); 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\n ► {}{}\n", rd.format_message(2), rd.format_source_name(), rd.format_source_line(4), )); }
s }
fn format_source_line(&self, level: usize) -> String { format_maybe_source_line( self.source_line.clone(), self.line_number, self.start_column, self.end_column, match self.category { DiagnosticCategory::Error => true, _ => false, }, level, ) }
fn format_source_name(&self) -> String { format_maybe_source_name( self.script_resource_name.clone(), self.line_number, self.start_column, ) }}
impl fmt::Display for DiagnosticItem { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "{}{}\n\n► {}{}{}", self.format_category_and_code(), self.format_message(0), self.format_source_name(), self.format_source_line(0), self.format_related_info(), ) }}
#[derive(Debug, PartialEq, Clone)]pub struct DiagnosticMessageChain { pub message: String, pub code: i64, pub category: DiagnosticCategory, pub next: Option<Vec<DiagnosticMessageChain>>,}
impl DiagnosticMessageChain { fn from_value(v: &serde_json::Value) -> Self { 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_next_array(n), _ => None, };
Self { message, code, category, next, } }
fn from_next_array(v: &serde_json::Value) -> Option<Vec<Self>> { if !v.is_array() { return None; }
let vec = v .as_array() .unwrap() .iter() .map(|item| Self::from_value(&item)) .collect::<Vec<Self>>();
Some(vec) }
pub fn from_json_value(v: &serde_json::Value) -> Option<Self> { if !v.is_object() { return None; }
Some(Self::from_value(v)) }
pub fn format_message(&self, level: usize) -> String { let mut s = String::new();
s.push_str(&std::iter::repeat(" ").take(level * 2).collect::<String>()); s.push_str(&self.message); s.push('\n'); if self.next.is_some() { let arr = self.next.clone().unwrap(); for dm in arr { s.push_str(&dm.format_message(level + 1)); } }
s }}
#[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::colors::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(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(vec![DiagnosticMessageChain { message: "Types of parameters 'o' and 'r' are incompatible.".to_string(), code: 2328, category: DiagnosticCategory::Error, next: Some(vec![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 '{ a(): { b: number; }; }' is not assignable to type '{ a(): { b: string; }; }'.", "messageChain": { "message": "Type '{ a(): { b: number; }; }' is not assignable to type '{ a(): { b: string; }; }'.", "code": 2322, "category": 3, "next": [ { "message": "Types of property 'a' are incompatible.", "code": 2326, "category": 3 } ] }, "code": 2322, "category": 3, "startPosition": 352, "endPosition": 353, "sourceLine": "x = y;", "lineNumber": 29, "scriptResourceName": "/deno/tests/error_003_typescript.ts", "startColumn": 0, "endColumn": 1 } ] }"#, ).unwrap(); let r = Diagnostic::from_json_value(&v); let expected = Some( Diagnostic { items: vec![ DiagnosticItem { message: "Type \'{ a(): { b: number; }; }\' is not assignable to type \'{ a(): { b: string; }; }\'.".to_string(), message_chain: Some( DiagnosticMessageChain { message: "Type \'{ a(): { b: number; }; }\' is not assignable to type \'{ a(): { b: string; }; }\'.".to_string(), code: 2322, category: DiagnosticCategory::Error, next: Some(vec![ DiagnosticMessageChain { message: "Types of property \'a\' are incompatible.".to_string(), code: 2326, category: DiagnosticCategory::Error, next: None, } ]) } ), related_information: None, source_line: Some("x = y;".to_string()), line_number: Some(29), script_resource_name: Some("/deno/tests/error_003_typescript.ts".to_string()), start_position: Some(352), end_position: Some(353), category: DiagnosticCategory::Error, code: 2322, start_column: Some(0), end_column: Some(1) } ] } ); 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 = "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\n► deno/tests/complex_diagnostics.ts:19:3\n\n19 values: o => [\n ~~~~~~\n\n The expected type comes from property \'values\' which is declared here on type \'SettingsInterface<B>\'\n\n ► deno/tests/complex_diagnostics.ts:7:3\n\n 7 values?: (r: T) => Array<Value<T>>;\n ~~~~~~\n\n"; assert_eq!(expected, strip_ansi_codes(&d.to_string())); }
#[test] fn diagnostic_to_string2() { let d = diagnostic2(); let expected = "error TS2322: Example 1\n\n► deno/tests/complex_diagnostics.ts:19:3\n\n19 values: o => [\n ~~~~~~\n\nerror TS2000: Example 2\n\n► /foo/bar.ts:129:3\n\n129 values: undefined,\n ~~~~~~\n\n\nFound 2 errors.\n"; assert_eq!(expected, strip_ansi_codes(&d.to_string())); }}