diff options
| author | David Sherret <dsherret@users.noreply.github.com> | 2022-11-25 18:29:48 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-11-25 18:29:48 -0500 |
| commit | dcb4ffb93a380710c32cc212b937ea38db5ceacc (patch) | |
| tree | 18bf860912a14b84287bb8dbafdc41c5e3cdc6ab /cli/diagnostics.rs | |
| parent | 0cc90d9246ff2c392457632d5030eaca2ca1ca6f (diff) | |
refactor: move dts files, diagnostics.rs, and tsc.rs to tsc folder (#16820)
Diffstat (limited to 'cli/diagnostics.rs')
| -rw-r--r-- | cli/diagnostics.rs | 595 |
1 files changed, 0 insertions, 595 deletions
diff --git a/cli/diagnostics.rs b/cli/diagnostics.rs deleted file mode 100644 index 05502dca4..000000000 --- a/cli/diagnostics.rs +++ /dev/null @@ -1,595 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use deno_runtime::colors; - -use deno_core::serde::Deserialize; -use deno_core::serde::Deserializer; -use deno_core::serde::Serialize; -use deno_core::serde::Serializer; -use once_cell::sync::Lazy; -use regex::Regex; -use std::error::Error; -use std::fmt; - -const MAX_SOURCE_LINE_LENGTH: usize = 150; - -const UNSTABLE_DENO_PROPS: &[&str] = &[ - "CreateHttpClientOptions", - "DatagramConn", - "HttpClient", - "UnixConnectOptions", - "UnixListenOptions", - "connect", - "createHttpClient", - "kill", - "listen", - "listenDatagram", - "dlopen", - "ppid", - "removeSignalListener", - "shutdown", - "umask", - "spawnChild", - "Child", - "spawn", - "spawnSync", - "SpawnOptions", - "ChildStatus", - "SpawnOutput", - "command", - "Command", - "CommandOptions", - "CommandStatus", - "CommandOutput", - "serve", - "ServeInit", - "ServeTlsInit", - "Handler", -]; - -static MSG_MISSING_PROPERTY_DENO: Lazy<Regex> = Lazy::new(|| { - Regex::new(r#"Property '([^']+)' does not exist on type 'typeof Deno'"#) - .unwrap() -}); - -static MSG_SUGGESTION: Lazy<Regex> = - Lazy::new(|| Regex::new(r#" Did you mean '([^']+)'\?"#).unwrap()); - -/// 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()); - } - } - } - - 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()); - } - } - } - - msg.to_string() - } - _ => msg.to_string(), - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum DiagnosticCategory { - Warning, - Error, - Suggestion, - Message, -} - -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 => "", - } - ) - } -} - -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)) - } -} - -impl Serialize for DiagnosticCategory { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - { - let value = match self { - DiagnosticCategory::Warning => 0_i32, - DiagnosticCategory::Error => 1_i32, - DiagnosticCategory::Suggestion => 2_i32, - DiagnosticCategory::Message => 3_i32, - }; - Serialize::serialize(&value, serializer) - } -} - -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), - } - } -} - -#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct DiagnosticMessageChain { - message_text: String, - category: DiagnosticCategory, - code: i64, - next: Option<Vec<DiagnosticMessageChain>>, -} - -impl DiagnosticMessageChain { - pub fn format_message(&self, level: usize) -> String { - let mut s = String::new(); - - s.push_str(&" ".repeat(level * 2)); - 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 - } -} - -#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct Position { - pub line: u64, - pub character: u64, -} - -#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct Diagnostic { - pub category: DiagnosticCategory, - pub code: u64, - pub start: Option<Position>, - pub end: Option<Position>, - pub message_text: Option<String>, - pub message_chain: Option<DiagnosticMessageChain>, - pub source: Option<String>, - pub source_line: Option<String>, - pub file_name: Option<String>, - pub related_information: Option<Vec<Diagnostic>>, -} - -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", - _ => "", - }; - - let code = if self.code >= 900001 { - "".to_string() - } else { - colors::bold(format!("TS{} ", self.code)).to_string() - }; - - if !category.is_empty() { - write!(f, "{}[{}]: ", code, category) - } else { - Ok(()) - } - } - - 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(()) - } - } - - 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, - ) - } - } - - 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)?; - } - } - - Ok(()) - } - - 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)?; - } - } - - 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 - } -} - -impl fmt::Display for Diagnostic { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.fmt_stack(f, 0)?; - self.fmt_related_information(f) - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct Diagnostics(Vec<Diagnostic>); - -impl Diagnostics { - #[cfg(test)] - pub fn new(diagnostics: Vec<Diagnostic>) -> Self { - Diagnostics(diagnostics) - } - - /// Return a set of diagnostics where only the values where the predicate - /// returns `true` are included. - pub fn filter<P>(&self, predicate: P) -> Self - where - P: FnMut(&Diagnostic) -> bool, - { - let diagnostics = self.0.clone().into_iter().filter(predicate).collect(); - Self(diagnostics) - } - - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -impl<'de> Deserialize<'de> for Diagnostics { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - let items: Vec<Diagnostic> = Deserialize::deserialize(deserializer)?; - Ok(Diagnostics(items)) - } -} - -impl Serialize for Diagnostics { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - { - Serialize::serialize(&self.0, serializer) - } -} - -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)?; - i += 1; - } - - if i > 1 { - write!(f, "\n\nFound {} errors.", i)?; - } - - Ok(()) - } -} - -impl Error for Diagnostics {} - -#[cfg(test)] -mod tests { - use super::*; - use deno_core::serde_json; - use deno_core::serde_json::json; - use test_util::strip_ansi_codes; - - #[test] - fn test_de_diagnostics() { - let value = json!([ - { - "messageText": "Unknown compiler option 'invalid'.", - "category": 1, - "code": 5023 - }, - { - "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 - }, - { - "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 - }, - { - "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": [ - { - "messageText": "Type 'number' is not assignable to type 'string'.", - "category": 1, - "code": 2322 - } - ] - } - ] - }, - "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 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 = diagnostics.to_string(); - assert_eq!( - strip_ansi_codes(&actual), - "TS5023 [ERROR]: Unknown compiler option \'invalid\'." - ); - } - - #[test] - 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 = diagnostics.to_string(); - 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_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 = diagnostics.to_string(); - 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"); - } -} |
