2020-01-02 15:13:47 -05:00
|
|
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
2019-06-19 22:07:01 -04:00
|
|
|
//! This mod provides DenoError to unify errors across Deno.
|
2019-09-15 14:48:25 -04:00
|
|
|
use crate::colors;
|
2019-07-10 18:53:48 -04:00
|
|
|
use crate::source_maps::apply_source_map;
|
|
|
|
use crate::source_maps::SourceMapGetter;
|
2020-01-05 11:56:18 -05:00
|
|
|
use deno_core::ErrBox;
|
2019-07-10 18:53:48 -04:00
|
|
|
use std::error::Error;
|
2019-06-19 22:07:01 -04:00
|
|
|
use std::fmt;
|
2020-03-02 17:20:16 -05:00
|
|
|
use std::ops::Deref;
|
2019-06-19 22:07:01 -04:00
|
|
|
|
2020-03-24 23:53:48 -04:00
|
|
|
const SOURCE_ABBREV_THRESHOLD: usize = 150;
|
|
|
|
|
2019-06-19 22:07:01 -04:00
|
|
|
/// A trait which specifies parts of a diagnostic like item needs to be able to
|
|
|
|
/// generate to conform its display to other diagnostic like items
|
|
|
|
pub trait DisplayFormatter {
|
|
|
|
fn format_category_and_code(&self) -> String;
|
|
|
|
fn format_message(&self, level: usize) -> String;
|
|
|
|
fn format_related_info(&self) -> String;
|
|
|
|
fn format_source_line(&self, level: usize) -> String;
|
|
|
|
fn format_source_name(&self) -> String;
|
|
|
|
}
|
|
|
|
|
2020-03-01 16:17:45 -05:00
|
|
|
fn format_source_name(
|
|
|
|
script_name: String,
|
2020-03-02 17:20:16 -05:00
|
|
|
line_number: i64,
|
2020-03-01 16:17:45 -05:00
|
|
|
column: i64,
|
|
|
|
) -> String {
|
2020-03-02 17:20:16 -05:00
|
|
|
let line_number = line_number + 1;
|
2020-03-01 16:17:45 -05:00
|
|
|
let column = column + 1;
|
2020-04-11 02:08:11 -04:00
|
|
|
let script_name_c = colors::cyan(script_name);
|
|
|
|
let line_c = colors::yellow(line_number.to_string());
|
|
|
|
let column_c = colors::yellow(column.to_string());
|
|
|
|
format!("{}:{}:{}", script_name_c, line_c, column_c)
|
2019-06-19 22:07:01 -04:00
|
|
|
}
|
|
|
|
|
2020-03-02 17:20:16 -05:00
|
|
|
/// Formats optional source, line number and column into a single string.
|
2019-06-19 22:07:01 -04:00
|
|
|
pub fn format_maybe_source_name(
|
|
|
|
script_name: Option<String>,
|
2020-03-02 17:20:16 -05:00
|
|
|
line_number: Option<i64>,
|
2019-06-19 22:07:01 -04:00
|
|
|
column: Option<i64>,
|
|
|
|
) -> String {
|
|
|
|
if script_name.is_none() {
|
|
|
|
return "".to_string();
|
|
|
|
}
|
|
|
|
|
2020-03-02 17:20:16 -05:00
|
|
|
assert!(line_number.is_some());
|
2019-06-19 22:07:01 -04:00
|
|
|
assert!(column.is_some());
|
2020-03-01 16:17:45 -05:00
|
|
|
format_source_name(
|
|
|
|
script_name.unwrap(),
|
2020-03-02 17:20:16 -05:00
|
|
|
line_number.unwrap(),
|
2020-03-01 16:17:45 -05:00
|
|
|
column.unwrap(),
|
|
|
|
)
|
2019-06-19 22:07:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Take an optional source line and associated information to format it into
|
|
|
|
/// a pretty printed version of that line.
|
|
|
|
pub fn format_maybe_source_line(
|
|
|
|
source_line: Option<String>,
|
|
|
|
line_number: Option<i64>,
|
|
|
|
start_column: Option<i64>,
|
|
|
|
end_column: Option<i64>,
|
|
|
|
is_error: bool,
|
|
|
|
level: usize,
|
|
|
|
) -> String {
|
|
|
|
if source_line.is_none() || line_number.is_none() {
|
|
|
|
return "".to_string();
|
|
|
|
}
|
|
|
|
|
|
|
|
let source_line = source_line.as_ref().unwrap();
|
|
|
|
// sometimes source_line gets set with an empty string, which then outputs
|
2020-03-24 23:53:48 -04:00
|
|
|
// an empty source line when displayed, so need just short circuit here.
|
|
|
|
// Also short-circuit on error line too long.
|
|
|
|
if source_line.is_empty() || source_line.len() > SOURCE_ABBREV_THRESHOLD {
|
2019-06-19 22:07:01 -04:00
|
|
|
return "".to_string();
|
|
|
|
}
|
|
|
|
|
|
|
|
assert!(start_column.is_some());
|
|
|
|
assert!(end_column.is_some());
|
2020-03-02 17:20:16 -05:00
|
|
|
let line_number = (1 + line_number.unwrap()).to_string();
|
|
|
|
let line_color = colors::black_on_white(line_number.to_string());
|
|
|
|
let line_number_len = line_number.len();
|
2019-06-19 22:07:01 -04:00
|
|
|
let line_padding =
|
2020-03-02 17:20:16 -05:00
|
|
|
colors::black_on_white(format!("{:indent$}", "", indent = line_number_len))
|
2019-06-19 22:07:01 -04:00
|
|
|
.to_string();
|
|
|
|
let mut s = String::new();
|
|
|
|
let start_column = start_column.unwrap();
|
|
|
|
let end_column = end_column.unwrap();
|
|
|
|
// TypeScript uses `~` always, but V8 would utilise `^` always, even when
|
|
|
|
// doing ranges, so here, if we only have one marker (very common with V8
|
|
|
|
// errors) we will use `^` instead.
|
|
|
|
let underline_char = if (end_column - start_column) <= 1 {
|
|
|
|
'^'
|
|
|
|
} else {
|
|
|
|
'~'
|
|
|
|
};
|
|
|
|
for i in 0..end_column {
|
|
|
|
if i >= start_column {
|
|
|
|
s.push(underline_char);
|
|
|
|
} else {
|
|
|
|
s.push(' ');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let color_underline = if is_error {
|
2019-09-15 14:48:25 -04:00
|
|
|
colors::red(s).to_string()
|
2019-06-19 22:07:01 -04:00
|
|
|
} else {
|
2019-09-15 14:48:25 -04:00
|
|
|
colors::cyan(s).to_string()
|
2019-06-19 22:07:01 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
let indent = format!("{:indent$}", "", indent = level);
|
|
|
|
|
|
|
|
format!(
|
|
|
|
"\n\n{}{} {}\n{}{} {}\n",
|
|
|
|
indent, line_color, source_line, indent, line_padding, color_underline
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Format a message to preface with `error: ` with ansi codes for red.
|
|
|
|
pub fn format_error_message(msg: String) -> String {
|
2019-09-15 14:48:25 -04:00
|
|
|
let preamble = colors::red("error:".to_string());
|
2019-06-19 22:07:01 -04:00
|
|
|
format!("{} {}", preamble, msg)
|
|
|
|
}
|
|
|
|
|
2020-03-02 17:20:16 -05:00
|
|
|
/// Wrapper around deno_core::JSError which provides color to_string.
|
2019-07-10 18:53:48 -04:00
|
|
|
#[derive(Debug)]
|
2020-03-02 17:20:16 -05:00
|
|
|
pub struct JSError(deno_core::JSError);
|
2019-07-10 18:53:48 -04:00
|
|
|
|
|
|
|
impl JSError {
|
2020-03-02 17:20:16 -05:00
|
|
|
pub fn create(
|
|
|
|
core_js_error: deno_core::JSError,
|
2019-07-10 18:53:48 -04:00
|
|
|
source_map_getter: &impl SourceMapGetter,
|
|
|
|
) -> ErrBox {
|
2020-03-02 17:20:16 -05:00
|
|
|
let core_js_error = apply_source_map(&core_js_error, source_map_getter);
|
|
|
|
let js_error = Self(core_js_error);
|
2019-07-10 18:53:48 -04:00
|
|
|
ErrBox::from(js_error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 17:20:16 -05:00
|
|
|
impl Deref for JSError {
|
|
|
|
type Target = deno_core::JSError;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.0
|
2020-01-17 18:43:53 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-10 18:53:48 -04:00
|
|
|
impl DisplayFormatter for JSError {
|
2019-06-19 22:07:01 -04:00
|
|
|
fn format_category_and_code(&self) -> String {
|
|
|
|
"".to_string()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn format_message(&self, _level: usize) -> String {
|
|
|
|
format!(
|
|
|
|
"{}{}",
|
2019-09-15 14:48:25 -04:00
|
|
|
colors::red_bold("error: ".to_string()),
|
2019-06-19 22:07:01 -04:00
|
|
|
self.0.message.clone()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn format_related_info(&self) -> String {
|
|
|
|
"".to_string()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn format_source_line(&self, level: usize) -> String {
|
|
|
|
format_maybe_source_line(
|
|
|
|
self.0.source_line.clone(),
|
|
|
|
self.0.line_number,
|
|
|
|
self.0.start_column,
|
|
|
|
self.0.end_column,
|
|
|
|
true,
|
|
|
|
level,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn format_source_name(&self) -> String {
|
2019-07-10 18:53:48 -04:00
|
|
|
let e = &self.0;
|
2019-06-19 22:07:01 -04:00
|
|
|
if e.script_resource_name.is_none() {
|
|
|
|
return "".to_string();
|
|
|
|
}
|
|
|
|
|
|
|
|
format!(
|
|
|
|
"\n► {}",
|
|
|
|
format_maybe_source_name(
|
|
|
|
e.script_resource_name.clone(),
|
|
|
|
e.line_number,
|
|
|
|
e.start_column,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-10 18:53:48 -04:00
|
|
|
impl fmt::Display for JSError {
|
2019-06-19 22:07:01 -04:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"{}{}{}",
|
|
|
|
self.format_message(0),
|
|
|
|
self.format_source_name(),
|
|
|
|
self.format_source_line(0),
|
|
|
|
)?;
|
2020-04-11 02:08:11 -04:00
|
|
|
for formatted_frame in &self.0.formatted_frames {
|
|
|
|
write!(f, "\n at {}", formatted_frame)?;
|
2019-06-19 22:07:01 -04:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-10 18:53:48 -04:00
|
|
|
impl Error for JSError {}
|
2019-06-19 22:07:01 -04:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2019-09-15 14:48:25 -04:00
|
|
|
use crate::colors::strip_ansi_codes;
|
2019-06-19 22:07:01 -04:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_format_none_source_name() {
|
|
|
|
let actual = format_maybe_source_name(None, None, None);
|
|
|
|
assert_eq!(actual, "");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_format_some_source_name() {
|
|
|
|
let actual = format_maybe_source_name(
|
|
|
|
Some("file://foo/bar.ts".to_string()),
|
|
|
|
Some(1),
|
|
|
|
Some(2),
|
|
|
|
);
|
|
|
|
assert_eq!(strip_ansi_codes(&actual), "file://foo/bar.ts:2:3");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_format_none_source_line() {
|
|
|
|
let actual = format_maybe_source_line(None, None, None, None, false, 0);
|
|
|
|
assert_eq!(actual, "");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_format_some_source_line() {
|
|
|
|
let actual = format_maybe_source_line(
|
|
|
|
Some("console.log('foo');".to_string()),
|
|
|
|
Some(8),
|
|
|
|
Some(8),
|
|
|
|
Some(11),
|
|
|
|
true,
|
|
|
|
0,
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
strip_ansi_codes(&actual),
|
|
|
|
"\n\n9 console.log(\'foo\');\n ~~~\n"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_format_error_message() {
|
|
|
|
let actual = format_error_message("foo".to_string());
|
|
|
|
assert_eq!(strip_ansi_codes(&actual), "error: foo");
|
|
|
|
}
|
|
|
|
}
|