mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 07:14:47 -05:00
refactor: improve tsc diagnostics (#7420)
This commit is contained in:
parent
5276cc8592
commit
10fbfcbc79
8 changed files with 684 additions and 715 deletions
|
@ -1,240 +1,152 @@
|
|||
// 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)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for Diagnostic {
|
||||
fn description(&self) -> &str {
|
||||
&self.items[0].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,
|
||||
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
fn format_category_and_code(
|
||||
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)
|
||||
}
|
||||
|
||||
fn format_message(
|
||||
message_chain: &Option<DiagnosticMessageChain>,
|
||||
message: &str,
|
||||
level: usize,
|
||||
) -> String {
|
||||
debug!("format_message");
|
||||
|
||||
if let Some(message_chain) = message_chain {
|
||||
let mut s = message_chain.format_message(level);
|
||||
s.pop();
|
||||
|
||||
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());
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fn format_maybe_related_information(
|
||||
related_information: &Option<Vec<DiagnosticItem>>,
|
||||
) -> String {
|
||||
if related_information.is_none() {
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
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,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
format_maybe_related_information(&self.related_information),
|
||||
)
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
#[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>>,
|
||||
}
|
||||
|
||||
impl DiagnosticMessageChain {
|
||||
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 let Some(next) = &self.next {
|
||||
let arr = next.clone();
|
||||
for dm in arr {
|
||||
s.push_str(&dm.format_message(level + 1));
|
||||
}
|
||||
}
|
||||
|
||||
s
|
||||
msg.to_string()
|
||||
}
|
||||
_ => msg.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum DiagnosticCategory {
|
||||
Log, // 0
|
||||
Debug, // 1
|
||||
Info, // 2
|
||||
Error, // 3
|
||||
Warning, // 4
|
||||
Suggestion, // 5
|
||||
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 {
|
||||
|
@ -250,202 +162,464 @@ impl<'de> Deserialize<'de> for DiagnosticCategory {
|
|||
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,
|
||||
0 => DiagnosticCategory::Warning,
|
||||
1 => DiagnosticCategory::Error,
|
||||
2 => DiagnosticCategory::Suggestion,
|
||||
3 => DiagnosticCategory::Message,
|
||||
_ => panic!("Unknown value: {}", value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[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(&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
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Position {
|
||||
pub line: u64,
|
||||
pub character: u64,
|
||||
}
|
||||
|
||||
#[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>>,
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
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 items: Vec<Diagnostic> = Deserialize::deserialize(deserializer)?;
|
||||
Ok(Diagnostics(items))
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
])
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
use colors::strip_ansi_codes;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn from_json() {
|
||||
let r = serde_json::from_str::<Diagnostic>(
|
||||
&r#"{
|
||||
"items": [
|
||||
fn test_de_diagnostics() {
|
||||
let value = json!([
|
||||
{
|
||||
"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,
|
||||
"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": [
|
||||
{
|
||||
"message": "Types of property 'a' are incompatible.",
|
||||
"code": 2326,
|
||||
"category": 3
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"code": 2322,
|
||||
"category": 3,
|
||||
"startPosition": 352,
|
||||
"endPosition": 353,
|
||||
"sourceLine": "x = y;",
|
||||
"lineNumber": 29,
|
||||
"scriptResourceName": "/deno/tests/error_003_typescript.ts",
|
||||
"startColumn": 0,
|
||||
"endColumn": 1
|
||||
"code": 2322,
|
||||
"category": 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);
|
||||
]);
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
54
cli/dts/lib.deno.unstable.d.ts
vendored
54
cli/dts/lib.deno.unstable.d.ts
vendored
|
@ -188,12 +188,10 @@ declare namespace Deno {
|
|||
|
||||
/** The log category for a diagnostic message. */
|
||||
export enum DiagnosticCategory {
|
||||
Log = 0,
|
||||
Debug = 1,
|
||||
Info = 2,
|
||||
Error = 3,
|
||||
Warning = 4,
|
||||
Suggestion = 5,
|
||||
Warning = 0,
|
||||
Error = 1,
|
||||
Suggestion = 2,
|
||||
Message = 3,
|
||||
}
|
||||
|
||||
export interface DiagnosticMessageChain {
|
||||
|
@ -203,37 +201,33 @@ declare namespace Deno {
|
|||
next?: DiagnosticMessageChain[];
|
||||
}
|
||||
|
||||
export interface DiagnosticItem {
|
||||
export interface Diagnostic {
|
||||
/** A string message summarizing the diagnostic. */
|
||||
message: string;
|
||||
messageText?: 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[];
|
||||
relatedInformation?: Diagnostic[];
|
||||
/** 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;
|
||||
source?: string;
|
||||
/** The start position of the error. Zero based index. */
|
||||
start?: {
|
||||
line: number;
|
||||
character: number;
|
||||
};
|
||||
/** The end position of the error. Zero based index. */
|
||||
end?: {
|
||||
line: number;
|
||||
character: number;
|
||||
};
|
||||
/** The filename of the resource related to the diagnostic message. */
|
||||
fileName?: string;
|
||||
/** 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[];
|
||||
}
|
||||
|
||||
/** **UNSTABLE**: new API, yet to be vetted.
|
||||
|
@ -247,9 +241,9 @@ declare namespace Deno {
|
|||
* console.log(Deno.formatDiagnostics(diagnostics)); // User friendly output of diagnostics
|
||||
* ```
|
||||
*
|
||||
* @param items An array of diagnostic items to format
|
||||
* @param diagnostics An array of diagnostic items to format
|
||||
*/
|
||||
export function formatDiagnostics(items: DiagnosticItem[]): string;
|
||||
export function formatDiagnostics(diagnostics: Diagnostic[]): string;
|
||||
|
||||
/** **UNSTABLE**: new API, yet to be vetted.
|
||||
*
|
||||
|
@ -530,7 +524,7 @@ declare namespace Deno {
|
|||
rootName: string,
|
||||
sources?: Record<string, string>,
|
||||
options?: CompilerOptions,
|
||||
): Promise<[DiagnosticItem[] | undefined, Record<string, string>]>;
|
||||
): Promise<[Diagnostic[] | undefined, Record<string, string>]>;
|
||||
|
||||
/** **UNSTABLE**: new API, yet to be vetted.
|
||||
*
|
||||
|
@ -573,7 +567,7 @@ declare namespace Deno {
|
|||
rootName: string,
|
||||
sources?: Record<string, string>,
|
||||
options?: CompilerOptions,
|
||||
): Promise<[DiagnosticItem[] | undefined, string]>;
|
||||
): Promise<[Diagnostic[] | undefined, string]>;
|
||||
|
||||
/** **UNSTABLE**: Should not have same name as `window.location` type. */
|
||||
interface Location {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use crate::diagnostics::Diagnostic;
|
||||
use crate::diagnostics::Diagnostics;
|
||||
use crate::source_maps::get_orig_position;
|
||||
use crate::source_maps::CachedMaps;
|
||||
use deno_core::ErrBox;
|
||||
|
@ -52,6 +52,6 @@ fn op_format_diagnostic(
|
|||
args: Value,
|
||||
_zero_copy: &mut [ZeroCopyBuf],
|
||||
) -> Result<Value, ErrBox> {
|
||||
let diagnostic = serde_json::from_value::<Diagnostic>(args)?;
|
||||
let diagnostic: Diagnostics = serde_json::from_value(args)?;
|
||||
Ok(json!(diagnostic.to_string()))
|
||||
}
|
||||
|
|
|
@ -6,19 +6,15 @@
|
|||
|
||||
((window) => {
|
||||
const DiagnosticCategory = {
|
||||
0: "Log",
|
||||
1: "Debug",
|
||||
2: "Info",
|
||||
3: "Error",
|
||||
4: "Warning",
|
||||
5: "Suggestion",
|
||||
0: "Warning",
|
||||
1: "Error",
|
||||
2: "Suggestion",
|
||||
3: "Message",
|
||||
|
||||
Log: 0,
|
||||
Debug: 1,
|
||||
Info: 2,
|
||||
Error: 3,
|
||||
Warning: 4,
|
||||
Suggestion: 5,
|
||||
Warning: 0,
|
||||
Error: 1,
|
||||
Suggestion: 2,
|
||||
Message: 3,
|
||||
};
|
||||
|
||||
window.__bootstrap.diagnostics = {
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
const internals = window.__bootstrap.internals;
|
||||
const dispatchJson = window.__bootstrap.dispatchJson;
|
||||
|
||||
function opFormatDiagnostics(items) {
|
||||
return dispatchJson.sendSync("op_format_diagnostic", { items });
|
||||
function opFormatDiagnostics(diagnostics) {
|
||||
return dispatchJson.sendSync("op_format_diagnostic", diagnostics);
|
||||
}
|
||||
|
||||
function opApplySourceMap(location) {
|
||||
|
|
|
@ -2,27 +2,33 @@
|
|||
import { assert, unitTest } from "./test_util.ts";
|
||||
|
||||
unitTest(function formatDiagnosticBasic() {
|
||||
const fixture: Deno.DiagnosticItem[] = [
|
||||
const fixture: Deno.Diagnostic[] = [
|
||||
{
|
||||
message: "Example error",
|
||||
category: Deno.DiagnosticCategory.Error,
|
||||
sourceLine: "abcdefghijklmnopqrstuv",
|
||||
lineNumber: 1000,
|
||||
scriptResourceName: "foo.ts",
|
||||
startColumn: 1,
|
||||
endColumn: 2,
|
||||
code: 4000,
|
||||
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,
|
||||
},
|
||||
];
|
||||
const out = Deno.formatDiagnostics(fixture);
|
||||
assert(out.includes("Example error"));
|
||||
assert(out.includes("foo.ts"));
|
||||
assert(out.includes("Cannot find name"));
|
||||
assert(out.includes("test.ts"));
|
||||
});
|
||||
|
||||
unitTest(function formatDiagnosticError() {
|
||||
let thrown = false;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const bad = ([{ hello: 123 }] as any) as Deno.DiagnosticItem[];
|
||||
const bad = ([{ hello: 123 }] as any) as Deno.Diagnostic[];
|
||||
try {
|
||||
Deno.formatDiagnostics(bad);
|
||||
} catch (e) {
|
||||
|
|
17
cli/tsc.rs
17
cli/tsc.rs
|
@ -1,8 +1,7 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use crate::colors;
|
||||
use crate::diagnostics::Diagnostic;
|
||||
use crate::diagnostics::DiagnosticItem;
|
||||
use crate::diagnostics::Diagnostics;
|
||||
use crate::disk_cache::DiskCache;
|
||||
use crate::file_fetcher::SourceFile;
|
||||
use crate::file_fetcher::SourceFileFetcher;
|
||||
|
@ -396,7 +395,7 @@ struct EmittedSource {
|
|||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct BundleResponse {
|
||||
diagnostics: Diagnostic,
|
||||
diagnostics: Diagnostics,
|
||||
bundle_output: Option<String>,
|
||||
stats: Option<Vec<Stat>>,
|
||||
}
|
||||
|
@ -404,7 +403,7 @@ struct BundleResponse {
|
|||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct CompileResponse {
|
||||
diagnostics: Diagnostic,
|
||||
diagnostics: Diagnostics,
|
||||
emit_map: HashMap<String, EmittedSource>,
|
||||
build_info: Option<String>,
|
||||
stats: Option<Vec<Stat>>,
|
||||
|
@ -425,14 +424,14 @@ struct TranspileTsOptions {
|
|||
#[serde(rename_all = "camelCase")]
|
||||
#[allow(unused)]
|
||||
struct RuntimeBundleResponse {
|
||||
diagnostics: Vec<DiagnosticItem>,
|
||||
diagnostics: Diagnostics,
|
||||
output: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct RuntimeCompileResponse {
|
||||
diagnostics: Vec<DiagnosticItem>,
|
||||
diagnostics: Diagnostics,
|
||||
emit_map: HashMap<String, EmittedSource>,
|
||||
}
|
||||
|
||||
|
@ -647,7 +646,7 @@ impl TsCompiler {
|
|||
|
||||
let compile_response: CompileResponse = serde_json::from_str(&json_str)?;
|
||||
|
||||
if !compile_response.diagnostics.items.is_empty() {
|
||||
if !compile_response.diagnostics.0.is_empty() {
|
||||
return Err(ErrBox::error(compile_response.diagnostics.to_string()));
|
||||
}
|
||||
|
||||
|
@ -769,7 +768,7 @@ impl TsCompiler {
|
|||
|
||||
maybe_log_stats(bundle_response.stats);
|
||||
|
||||
if !bundle_response.diagnostics.items.is_empty() {
|
||||
if !bundle_response.diagnostics.0.is_empty() {
|
||||
return Err(ErrBox::error(bundle_response.diagnostics.to_string()));
|
||||
}
|
||||
|
||||
|
@ -1287,7 +1286,7 @@ pub async fn runtime_compile(
|
|||
|
||||
let response: RuntimeCompileResponse = serde_json::from_str(&json_str)?;
|
||||
|
||||
if response.diagnostics.is_empty() && sources.is_none() {
|
||||
if response.diagnostics.0.is_empty() && sources.is_none() {
|
||||
compiler.cache_emitted_files(response.emit_map)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,262 +24,62 @@ delete Object.prototype.__proto__;
|
|||
const errorStack = window.__bootstrap.errorStack;
|
||||
const errors = window.__bootstrap.errors.errors;
|
||||
|
||||
function opNow() {
|
||||
const res = dispatchJson.sendSync("op_now");
|
||||
return res.seconds * 1e3 + res.subsecNanos / 1e6;
|
||||
/**
|
||||
* @param {import("../dts/typescript").DiagnosticRelatedInformation} diagnostic
|
||||
*/
|
||||
function fromRelatedInformation({
|
||||
start,
|
||||
length,
|
||||
file,
|
||||
messageText: msgText,
|
||||
...ri
|
||||
}) {
|
||||
let messageText;
|
||||
let messageChain;
|
||||
if (typeof msgText === "object") {
|
||||
messageChain = msgText;
|
||||
} else {
|
||||
messageText = msgText;
|
||||
}
|
||||
|
||||
const DiagnosticCategory = {
|
||||
0: "Log",
|
||||
1: "Debug",
|
||||
2: "Info",
|
||||
3: "Error",
|
||||
4: "Warning",
|
||||
5: "Suggestion",
|
||||
|
||||
Log: 0,
|
||||
Debug: 1,
|
||||
Info: 2,
|
||||
Error: 3,
|
||||
Warning: 4,
|
||||
Suggestion: 5,
|
||||
};
|
||||
|
||||
const unstableDenoGlobalProperties = [
|
||||
"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",
|
||||
];
|
||||
|
||||
function transformMessageText(messageText, code) {
|
||||
switch (code) {
|
||||
case 2339: {
|
||||
const property = messageText
|
||||
.replace(/^Property '/, "")
|
||||
.replace(/' does not exist on type 'typeof Deno'\./, "");
|
||||
|
||||
if (
|
||||
messageText.endsWith("on type 'typeof Deno'.") &&
|
||||
unstableDenoGlobalProperties.includes(property)
|
||||
) {
|
||||
return `${messageText} 'Deno.${property}' is an unstable API. Did you forget to run with the '--unstable' flag?`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2551: {
|
||||
const suggestionMessagePattern = / Did you mean '(.+)'\?$/;
|
||||
const property = messageText
|
||||
.replace(/^Property '/, "")
|
||||
.replace(/' does not exist on type 'typeof Deno'\./, "")
|
||||
.replace(suggestionMessagePattern, "");
|
||||
const suggestion = messageText.match(suggestionMessagePattern);
|
||||
const replacedMessageText = messageText.replace(
|
||||
suggestionMessagePattern,
|
||||
"",
|
||||
);
|
||||
if (suggestion && unstableDenoGlobalProperties.includes(property)) {
|
||||
const suggestedProperty = suggestion[1];
|
||||
return `${replacedMessageText} 'Deno.${property}' is an unstable API. Did you forget to run with the '--unstable' flag, or did you mean '${suggestedProperty}'?`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return messageText;
|
||||
}
|
||||
|
||||
function fromDiagnosticCategory(category) {
|
||||
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, start, length) {
|
||||
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", " ");
|
||||
if (start !== undefined && length !== undefined && file) {
|
||||
const startPos = file.getLineAndCharacterOfPosition(start);
|
||||
const sourceLine = file.getFullText().split("\n")[startPos.line];
|
||||
const fileName = file.fileName;
|
||||
return {
|
||||
start: startPos,
|
||||
end: file.getLineAndCharacterOfPosition(start + length),
|
||||
fileName,
|
||||
messageChain,
|
||||
messageText,
|
||||
sourceLine,
|
||||
lineNumber,
|
||||
scriptResourceName,
|
||||
startColumn,
|
||||
endColumn,
|
||||
...ri,
|
||||
};
|
||||
}
|
||||
|
||||
function fromDiagnosticMessageChain(messageChain) {
|
||||
if (!messageChain) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return messageChain.map(({ messageText, code, category, next }) => {
|
||||
const message = transformMessageText(messageText, code);
|
||||
} else {
|
||||
return {
|
||||
message,
|
||||
code,
|
||||
category: fromDiagnosticCategory(category),
|
||||
next: fromDiagnosticMessageChain(next),
|
||||
messageChain,
|
||||
messageText,
|
||||
...ri,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../dts/typescript").Diagnostic[]} diagnostics
|
||||
*/
|
||||
function fromTypeScriptDiagnostic(diagnostics) {
|
||||
return diagnostics.map(({ relatedInformation: ri, source, ...diag }) => {
|
||||
const value = fromRelatedInformation(diag);
|
||||
value.relatedInformation = ri
|
||||
? ri.map(fromRelatedInformation)
|
||||
: undefined;
|
||||
value.source = source;
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
function parseDiagnostic(item) {
|
||||
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;
|
||||
let messageChain;
|
||||
if (typeof messageText === "string") {
|
||||
message = transformMessageText(messageText, code);
|
||||
} else {
|
||||
message = transformMessageText(messageText.messageText, messageText.code);
|
||||
messageChain = fromDiagnosticMessageChain([messageText])[0];
|
||||
}
|
||||
|
||||
const base = {
|
||||
message,
|
||||
messageChain,
|
||||
code,
|
||||
category,
|
||||
startPosition,
|
||||
endPosition,
|
||||
};
|
||||
|
||||
return sourceInfo ? { ...base, ...sourceInfo } : base;
|
||||
}
|
||||
|
||||
function parseRelatedInformation(relatedInformation) {
|
||||
const result = [];
|
||||
for (const item of relatedInformation) {
|
||||
result.push(parseDiagnostic(item));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function fromTypeScriptDiagnostic(diagnostics) {
|
||||
const items = [];
|
||||
for (const sourceDiagnostic of diagnostics) {
|
||||
const item = parseDiagnostic(sourceDiagnostic);
|
||||
if (sourceDiagnostic.relatedInformation) {
|
||||
item.relatedInformation = parseRelatedInformation(
|
||||
sourceDiagnostic.relatedInformation,
|
||||
);
|
||||
}
|
||||
items.push(item);
|
||||
}
|
||||
return { items };
|
||||
function opNow() {
|
||||
const res = dispatchJson.sendSync("op_now");
|
||||
return res.seconds * 1e3 + res.subsecNanos / 1e6;
|
||||
}
|
||||
|
||||
// We really don't want to depend on JSON dispatch during snapshotting, so
|
||||
|
@ -1353,7 +1153,7 @@ delete Object.prototype.__proto__;
|
|||
});
|
||||
|
||||
const maybeDiagnostics = diagnostics.length
|
||||
? fromTypeScriptDiagnostic(diagnostics).items
|
||||
? fromTypeScriptDiagnostic(diagnostics)
|
||||
: [];
|
||||
|
||||
return {
|
||||
|
@ -1413,7 +1213,7 @@ delete Object.prototype.__proto__;
|
|||
});
|
||||
|
||||
const maybeDiagnostics = diagnostics.length
|
||||
? fromTypeScriptDiagnostic(diagnostics).items
|
||||
? fromTypeScriptDiagnostic(diagnostics)
|
||||
: [];
|
||||
|
||||
return {
|
||||
|
|
Loading…
Reference in a new issue