diff options
Diffstat (limited to 'cli/diagnostics.rs')
-rw-r--r-- | cli/diagnostics.rs | 898 |
1 files changed, 536 insertions, 362 deletions
diff --git a/cli/diagnostics.rs b/cli/diagnostics.rs index 41ee5ec22..083c11bf5 100644 --- a/cli/diagnostics.rs +++ b/cli/diagnostics.rs @@ -1,451 +1,625 @@ // 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. use crate::colors; -use crate::fmt_errors::format_stack; + +use regex::Regex; use serde::Deserialize; use serde::Deserializer; use std::error::Error; use std::fmt; -#[derive(Clone, Debug, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct Diagnostic { - pub items: Vec<DiagnosticItem>, +const MAX_SOURCE_LINE_LENGTH: usize = 150; + +const UNSTABLE_DENO_PROPS: &[&str] = &[ + "CompilerOptions", + "DatagramConn", + "Diagnostic", + "DiagnosticCategory", + "DiagnosticItem", + "DiagnosticMessageChain", + "EnvPermissionDescriptor", + "HrtimePermissionDescriptor", + "HttpClient", + "LinuxSignal", + "Location", + "MacOSSignal", + "NetPermissionDescriptor", + "PermissionDescriptor", + "PermissionName", + "PermissionState", + "PermissionStatus", + "Permissions", + "PluginPermissionDescriptor", + "ReadPermissionDescriptor", + "RunPermissionDescriptor", + "ShutdownMode", + "Signal", + "SignalStream", + "StartTlsOptions", + "SymlinkOptions", + "TranspileOnlyResult", + "UnixConnectOptions", + "UnixListenOptions", + "WritePermissionDescriptor", + "applySourceMap", + "bundle", + "compile", + "connect", + "consoleSize", + "createHttpClient", + "fdatasync", + "fdatasyncSync", + "formatDiagnostics", + "futime", + "futimeSync", + "fstat", + "fstatSync", + "fsync", + "fsyncSync", + "ftruncate", + "ftruncateSync", + "hostname", + "kill", + "link", + "linkSync", + "listen", + "listenDatagram", + "loadavg", + "mainModule", + "openPlugin", + "osRelease", + "permissions", + "ppid", + "setRaw", + "shutdown", + "signal", + "signals", + "startTls", + "symlink", + "symlinkSync", + "transpileOnly", + "umask", + "utime", + "utimeSync", +]; + +lazy_static! { + static ref MSG_MISSING_PROPERTY_DENO: Regex = + Regex::new(r#"Property '([^']+)' does not exist on type 'typeof Deno'"#) + .unwrap(); + static ref MSG_SUGGESTION: Regex = + Regex::new(r#" Did you mean '([^']+)'\?"#).unwrap(); } -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 { - write!(f, "\n\n")?; +/// Potentially convert a "raw" diagnostic message from TSC to something that +/// provides a more sensible error message given a Deno runtime context. +fn format_message(msg: &str, code: &u64) -> String { + match code { + 2339 => { + if let Some(captures) = MSG_MISSING_PROPERTY_DENO.captures(msg) { + if let Some(property) = captures.get(1) { + if UNSTABLE_DENO_PROPS.contains(&property.as_str()) { + return format!("{} 'Deno.{}' is an unstable API. Did you forget to run with the '--unstable' flag?", msg, property.as_str()); + } + } } - write!(f, "{}", item.to_string())?; - i += 1; - } - if i > 1 { - write!(f, "\n\nFound {} errors.", i)?; + msg.to_string() } + 2551 => { + if let (Some(caps_property), Some(caps_suggestion)) = ( + MSG_MISSING_PROPERTY_DENO.captures(msg), + MSG_SUGGESTION.captures(msg), + ) { + if let (Some(property), Some(suggestion)) = + (caps_property.get(1), caps_suggestion.get(1)) + { + if UNSTABLE_DENO_PROPS.contains(&property.as_str()) { + return format!("{} 'Deno.{}' is an unstable API. Did you forget to run with the '--unstable' flag, or did you mean '{}'?", MSG_SUGGESTION.replace(msg, ""), property.as_str(), suggestion.as_str()); + } + } + } - Ok(()) + msg.to_string() + } + _ => msg.to_string(), } } -impl Error for Diagnostic { - fn description(&self) -> &str { - &self.items[0].message - } +#[derive(Clone, Debug, PartialEq)] +pub enum DiagnosticCategory { + Warning, + Error, + Suggestion, + Message, } -#[derive(Clone, Debug, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -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, +impl fmt::Display for DiagnosticCategory { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}", + match self { + DiagnosticCategory::Warning => "WARN ", + DiagnosticCategory::Error => "ERROR ", + DiagnosticCategory::Suggestion => "", + DiagnosticCategory::Message => "", + } + ) + } +} - /// Zero-based index to the start column on `line_number`. - pub start_column: Option<i64>, +impl<'de> Deserialize<'de> for DiagnosticCategory { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let s: i64 = Deserialize::deserialize(deserializer)?; + Ok(DiagnosticCategory::from(s)) + } +} - /// Zero-based index to the end column on `line_number`. - pub end_column: Option<i64>, +impl From<i64> for DiagnosticCategory { + fn from(value: i64) -> Self { + match value { + 0 => DiagnosticCategory::Warning, + 1 => DiagnosticCategory::Error, + 2 => DiagnosticCategory::Suggestion, + 3 => DiagnosticCategory::Message, + _ => panic!("Unknown value: {}", value), + } + } } -fn format_category_and_code( - category: &DiagnosticCategory, +#[derive(Debug, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct DiagnosticMessageChain { + message_text: String, + category: DiagnosticCategory, code: i64, -) -> String { - let category = match category { - DiagnosticCategory::Error => "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{}", code.to_string())).to_string(); - - format!("{} [{}]", code, category) + next: Option<Vec<DiagnosticMessageChain>>, } -fn format_message( - message_chain: &Option<DiagnosticMessageChain>, - message: &str, - level: usize, -) -> String { - debug!("format_message"); +impl DiagnosticMessageChain { + pub fn format_message(&self, level: usize) -> String { + let mut s = String::new(); - if let Some(message_chain) = message_chain { - let mut s = message_chain.format_message(level); - s.pop(); + s.push_str(&std::iter::repeat(" ").take(level * 2).collect::<String>()); + s.push_str(&self.message_text); + if let Some(next) = &self.next { + s.push('\n'); + let arr = next.clone(); + for dm in arr { + s.push_str(&dm.format_message(level + 1)); + } + } s - } else { - format!("{:indent$}{}", "", message, indent = level) } } -/// Formats optional source, line and column numbers into a single string. -fn format_maybe_frame( - file_name: Option<&str>, - line_number: Option<i64>, - column_number: Option<i64>, -) -> String { - if file_name.is_none() { - return "".to_string(); - } - - assert!(line_number.is_some()); - assert!(column_number.is_some()); +#[derive(Debug, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Position { + pub line: u64, + pub character: u64, +} - let line_number = line_number.unwrap(); - let column_number = column_number.unwrap(); - let file_name_c = colors::cyan(file_name.unwrap()); - let line_c = colors::yellow(&line_number.to_string()); - let column_c = colors::yellow(&column_number.to_string()); - format!("{}:{}:{}", file_name_c, line_c, column_c) +#[derive(Debug, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Diagnostic { + category: DiagnosticCategory, + code: u64, + start: Option<Position>, + end: Option<Position>, + message_text: Option<String>, + message_chain: Option<DiagnosticMessageChain>, + source: Option<String>, + source_line: Option<String>, + file_name: Option<String>, + related_information: Option<Vec<Diagnostic>>, } -fn format_maybe_related_information( - related_information: &Option<Vec<DiagnosticItem>>, -) -> String { - if related_information.is_none() { - return "".to_string(); +impl Diagnostic { + fn fmt_category_and_code(&self, f: &mut fmt::Formatter) -> fmt::Result { + let category = match self.category { + DiagnosticCategory::Error => "ERROR", + DiagnosticCategory::Warning => "WARN", + _ => "", + }; + + if !category.is_empty() { + write!( + f, + "{} [{}]: ", + colors::bold(&format!("TS{}", self.code)), + category + ) + } else { + Ok(()) + } } - let mut s = String::new(); - - if let Some(related_information) = related_information { - for rd in related_information { - s.push_str("\n\n"); - s.push_str(&format_stack( - matches!(rd.category, DiagnosticCategory::Error), - &format_message(&rd.message_chain, &rd.message, 0), - rd.source_line.as_deref(), - rd.start_column, - rd.end_column, - // Formatter expects 1-based line and column numbers, but ours are 0-based. - &[format_maybe_frame( - rd.script_resource_name.as_deref(), - rd.line_number.map(|n| n + 1), - rd.start_column.map(|n| n + 1), - )], - 4, - )); + fn fmt_frame(&self, f: &mut fmt::Formatter, level: usize) -> fmt::Result { + if let (Some(file_name), Some(start)) = + (self.file_name.as_ref(), self.start.as_ref()) + { + write!( + f, + "\n{:indent$} at {}:{}:{}", + "", + colors::cyan(file_name), + colors::yellow(&(start.line + 1).to_string()), + colors::yellow(&(start.character + 1).to_string()), + indent = level + ) + } else { + Ok(()) } } - s -} - -impl fmt::Display for DiagnosticItem { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}", - format_stack( - matches!(self.category, DiagnosticCategory::Error), - &format!( - "{}: {}", - format_category_and_code(&self.category, self.code), - format_message(&self.message_chain, &self.message, 0) - ), - self.source_line.as_deref(), - self.start_column, - self.end_column, - // Formatter expects 1-based line and column numbers, but ours are 0-based. - &[format_maybe_frame( - self.script_resource_name.as_deref(), - self.line_number.map(|n| n + 1), - self.start_column.map(|n| n + 1) - )], - 0 + fn fmt_message(&self, f: &mut fmt::Formatter, level: usize) -> fmt::Result { + if let Some(message_chain) = &self.message_chain { + write!(f, "{}", message_chain.format_message(level)) + } else { + write!( + f, + "{:indent$}{}", + "", + format_message(&self.message_text.clone().unwrap(), &self.code), + indent = level, ) - )?; - write!( - f, - "{}", - format_maybe_related_information(&self.related_information), - ) + } } -} -#[derive(Clone, Debug, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct DiagnosticMessageChain { - pub message: String, - pub code: i64, - pub category: DiagnosticCategory, - pub next: Option<Vec<DiagnosticMessageChain>>, -} + fn fmt_source_line( + &self, + f: &mut fmt::Formatter, + level: usize, + ) -> fmt::Result { + if let (Some(source_line), Some(start), Some(end)) = + (&self.source_line, &self.start, &self.end) + { + if !source_line.is_empty() && source_line.len() <= MAX_SOURCE_LINE_LENGTH + { + write!(f, "\n{:indent$}{}", "", source_line, indent = level)?; + let length = if start.line == end.line { + end.character - start.character + } else { + 1 + }; + let mut s = String::new(); + for i in 0..start.character { + s.push(if source_line.chars().nth(i as usize).unwrap() == '\t' { + '\t' + } else { + ' ' + }); + } + // TypeScript always uses `~` when underlining, but v8 always uses `^`. + // We will use `^` to indicate a single point, or `~` when spanning + // multiple characters. + let ch = if length > 1 { '~' } else { '^' }; + for _i in 0..length { + s.push(ch) + } + let underline = if self.is_error() { + colors::red(&s).to_string() + } else { + colors::cyan(&s).to_string() + }; + write!(f, "\n{:indent$}{}", "", underline, indent = level)?; + } + } -impl DiagnosticMessageChain { - pub fn format_message(&self, level: usize) -> String { - let mut s = String::new(); + Ok(()) + } - s.push_str(&std::iter::repeat(" ").take(level * 2).collect::<String>()); - s.push_str(&self.message); - s.push('\n'); - if let Some(next) = &self.next { - let arr = next.clone(); - for dm in arr { - s.push_str(&dm.format_message(level + 1)); + fn fmt_related_information(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(related_information) = self.related_information.as_ref() { + write!(f, "\n\n")?; + for info in related_information { + info.fmt_stack(f, 4)?; } } - s + Ok(()) + } + + fn fmt_stack(&self, f: &mut fmt::Formatter, level: usize) -> fmt::Result { + self.fmt_category_and_code(f)?; + self.fmt_message(f, level)?; + self.fmt_source_line(f, level)?; + self.fmt_frame(f, level) + } + + fn is_error(&self) -> bool { + self.category == DiagnosticCategory::Error } } -#[derive(Clone, Debug, PartialEq)] -pub enum DiagnosticCategory { - Log, // 0 - Debug, // 1 - Info, // 2 - Error, // 3 - Warning, // 4 - Suggestion, // 5 +impl fmt::Display for Diagnostic { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.fmt_stack(f, 0)?; + self.fmt_related_information(f) + } } -impl<'de> Deserialize<'de> for DiagnosticCategory { +#[derive(Clone, Debug)] +pub struct Diagnostics(pub Vec<Diagnostic>); + +impl<'de> Deserialize<'de> for Diagnostics { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>, { - let s: i64 = Deserialize::deserialize(deserializer)?; - Ok(DiagnosticCategory::from(s)) + let items: Vec<Diagnostic> = Deserialize::deserialize(deserializer)?; + Ok(Diagnostics(items)) } } -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), +impl fmt::Display for Diagnostics { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut i = 0; + for item in &self.0 { + if i > 0 { + write!(f, "\n\n")?; + } + write!(f, "{}", item.to_string())?; + i += 1; } + + if i > 1 { + write!(f, "\n\nFound {} errors.", i)?; + } + + Ok(()) } } +impl Error for Diagnostics {} + #[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), - } - ]) - } - ] - } - } + use colors::strip_ansi_codes; + use serde_json::json; - 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, + #[test] + fn test_de_diagnostics() { + let value = json!([ + { + "messageText": "Unknown compiler option 'invalid'.", + "category": 1, + "code": 5023 + }, + { + "start": { + "line": 0, + "character": 0 }, - 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, + "end": { + "line": 0, + "character": 7 }, - ], - } - } - - #[test] - fn from_json() { - let r = serde_json::from_str::<Diagnostic>( - &r#"{ - "items": [ + "fileName": "test.ts", + "messageText": "Cannot find name 'console'. Do you need to change your target library? Try changing the `lib` compiler option to include 'dom'.", + "sourceLine": "console.log(\"a\");", + "category": 1, + "code": 2584 + }, + { + "start": { + "line": 7, + "character": 0 + }, + "end": { + "line": 7, + "character": 7 + }, + "fileName": "test.ts", + "messageText": "Cannot find name 'foo_Bar'. Did you mean 'foo_bar'?", + "sourceLine": "foo_Bar();", + "relatedInformation": [ { - "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, + "start": { + "line": 3, + "character": 9 + }, + "end": { + "line": 3, + "character": 16 + }, + "fileName": "test.ts", + "messageText": "'foo_bar' is declared here.", + "sourceLine": "function foo_bar() {", + "category": 3, + "code": 2728 + } + ], + "category": 1, + "code": 2552 + }, + { + "start": { + "line": 18, + "character": 0 + }, + "end": { + "line": 18, + "character": 1 + }, + "fileName": "test.ts", + "messageChain": { + "messageText": "Type '{ a: { b: { c(): { d: number; }; }; }; }' is not assignable to type '{ a: { b: { c(): { d: string; }; }; }; }'.", + "category": 1, + "code": 2322, + "next": [ + { + "messageText": "The types of 'a.b.c().d' are incompatible between these types.", + "category": 1, + "code": 2200, "next": [ { - "message": "Types of property 'a' are incompatible.", - "code": 2326, - "category": 3 + "messageText": "Type 'number' is not assignable to type 'string'.", + "category": 1, + "code": 2322 } ] - }, - "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 expected = - 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); + } + ] + }, + "sourceLine": "x = y;", + "code": 2322, + "category": 1 + } + ]); + let diagnostics: Diagnostics = + serde_json::from_value(value).expect("cannot deserialize"); + assert_eq!(diagnostics.0.len(), 4); + assert!(diagnostics.0[0].source_line.is_none()); + assert!(diagnostics.0[0].file_name.is_none()); + assert!(diagnostics.0[0].start.is_none()); + assert!(diagnostics.0[0].end.is_none()); + assert!(diagnostics.0[0].message_text.is_some()); + assert!(diagnostics.0[0].message_chain.is_none()); + assert!(diagnostics.0[0].related_information.is_none()); + assert!(diagnostics.0[1].source_line.is_some()); + assert!(diagnostics.0[1].file_name.is_some()); + assert!(diagnostics.0[1].start.is_some()); + assert!(diagnostics.0[1].end.is_some()); + assert!(diagnostics.0[1].message_text.is_some()); + assert!(diagnostics.0[1].message_chain.is_none()); + assert!(diagnostics.0[1].related_information.is_none()); + assert!(diagnostics.0[2].source_line.is_some()); + assert!(diagnostics.0[2].file_name.is_some()); + assert!(diagnostics.0[2].start.is_some()); + assert!(diagnostics.0[2].end.is_some()); + assert!(diagnostics.0[2].message_text.is_some()); + assert!(diagnostics.0[2].message_chain.is_none()); + assert!(diagnostics.0[2].related_information.is_some()); } #[test] - fn diagnostic_to_string1() { - let d = diagnostic1(); - let expected = "TS2322 [ERROR]: 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 values: o => [\n ~~~~~~\n at deno/tests/complex_diagnostics.ts:19:3\n\n The expected type comes from property \'values\' which is declared here on type \'SettingsInterface<B>\'\n values?: (r: T) => Array<Value<T>>;\n ~~~~~~\n at deno/tests/complex_diagnostics.ts:7:3"; - assert_eq!(expected, strip_ansi_codes(&d.to_string())); + fn test_diagnostics_no_source() { + let value = json!([ + { + "messageText": "Unknown compiler option 'invalid'.", + "category":1, + "code":5023 + } + ]); + let diagnostics: Diagnostics = serde_json::from_value(value).unwrap(); + let actual = format!("{}", diagnostics); + assert_eq!( + strip_ansi_codes(&actual), + "TS5023 [ERROR]: Unknown compiler option \'invalid\'." + ); } #[test] - fn diagnostic_to_string2() { - let d = diagnostic2(); - let expected = "TS2322 [ERROR]: Example 1\n values: o => [\n ~~~~~~\n at deno/tests/complex_diagnostics.ts:19:3\n\nTS2000 [ERROR]: Example 2\n values: undefined,\n ~~~~~~\n at /foo/bar.ts:129:3\n\nFound 2 errors."; - assert_eq!(expected, strip_ansi_codes(&d.to_string())); + fn test_diagnostics_basic() { + let value = json!([ + { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 0, + "character": 7 + }, + "fileName": "test.ts", + "messageText": "Cannot find name 'console'. Do you need to change your target library? Try changing the `lib` compiler option to include 'dom'.", + "sourceLine": "console.log(\"a\");", + "category": 1, + "code": 2584 + } + ]); + let diagnostics: Diagnostics = serde_json::from_value(value).unwrap(); + let actual = format!("{}", diagnostics); + assert_eq!(strip_ansi_codes(&actual), "TS2584 [ERROR]: Cannot find name \'console\'. Do you need to change your target library? Try changing the `lib` compiler option to include \'dom\'.\nconsole.log(\"a\");\n~~~~~~~\n at test.ts:1:1"); } #[test] - fn test_format_none_frame() { - let actual = format_maybe_frame(None, None, None); - assert_eq!(actual, ""); + fn test_diagnostics_related_info() { + let value = json!([ + { + "start": { + "line": 7, + "character": 0 + }, + "end": { + "line": 7, + "character": 7 + }, + "fileName": "test.ts", + "messageText": "Cannot find name 'foo_Bar'. Did you mean 'foo_bar'?", + "sourceLine": "foo_Bar();", + "relatedInformation": [ + { + "start": { + "line": 3, + "character": 9 + }, + "end": { + "line": 3, + "character": 16 + }, + "fileName": "test.ts", + "messageText": "'foo_bar' is declared here.", + "sourceLine": "function foo_bar() {", + "category": 3, + "code": 2728 + } + ], + "category": 1, + "code": 2552 + } + ]); + let diagnostics: Diagnostics = serde_json::from_value(value).unwrap(); + let actual = format!("{}", diagnostics); + assert_eq!(strip_ansi_codes(&actual), "TS2552 [ERROR]: Cannot find name \'foo_Bar\'. Did you mean \'foo_bar\'?\nfoo_Bar();\n~~~~~~~\n at test.ts:8:1\n\n \'foo_bar\' is declared here.\n function foo_bar() {\n ~~~~~~~\n at test.ts:4:10"); } #[test] - fn test_format_some_frame() { - let actual = - format_maybe_frame(Some("file://foo/bar.ts"), Some(1), Some(2)); - assert_eq!(strip_ansi_codes(&actual), "file://foo/bar.ts:1:2"); + fn test_unstable_suggestion() { + let value = json![ + { + "start": { + "line": 0, + "character": 17 + }, + "end": { + "line": 0, + "character": 21 + }, + "fileName": "file:///cli/tests/unstable_ts2551.ts", + "messageText": "Property 'ppid' does not exist on type 'typeof Deno'. Did you mean 'pid'?", + "sourceLine": "console.log(Deno.ppid);", + "relatedInformation": [ + { + "start": { + "line": 89, + "character": 15 + }, + "end": { + "line": 89, + "character": 18 + }, + "fileName": "asset:///lib.deno.ns.d.ts", + "messageText": "'pid' is declared here.", + "sourceLine": " export const pid: number;", + "category": 3, + "code": 2728 + } + ], + "category": 1, + "code": 2551 + } + ]; + let diagnostics: Diagnostic = serde_json::from_value(value).unwrap(); + let actual = format!("{}", diagnostics); + assert_eq!(strip_ansi_codes(&actual), "TS2551 [ERROR]: Property \'ppid\' does not exist on type \'typeof Deno\'. \'Deno.ppid\' is an unstable API. Did you forget to run with the \'--unstable\' flag, or did you mean \'pid\'?\nconsole.log(Deno.ppid);\n ~~~~\n at file:///cli/tests/unstable_ts2551.ts:1:18\n\n \'pid\' is declared here.\n export const pid: number;\n ~~~\n at asset:///lib.deno.ns.d.ts:90:16"); } } |