Skip to content

Commit

Permalink
Align formatting of errors.
Browse files Browse the repository at this point in the history
  • Loading branch information
kitsonk committed Jun 19, 2019
1 parent a280ef0 commit 663218d
Show file tree
Hide file tree
Showing 19 changed files with 377 additions and 217 deletions.
8 changes: 0 additions & 8 deletions cli/ansi.rs
Expand Up @@ -79,14 +79,6 @@ pub fn red(s: String) -> impl fmt::Display {
style.paint(s)
}

pub fn grey(s: String) -> impl fmt::Display {
let mut style = Style::new();
if use_color() {
style = style.fg(Fixed(8));
}
style.paint(s)
}

pub fn bold(s: String) -> impl fmt::Display {
let mut style = Style::new();
if use_color() {
Expand Down
14 changes: 14 additions & 0 deletions cli/deno_dir.rs
Expand Up @@ -349,6 +349,20 @@ impl SourceMapGetter for DenoDir {
},
}
}

fn get_source_line(&self, script_name: &str, line: usize) -> Option<String> {
match self.fetch_module_meta_data(script_name, ".", true, true) {
Ok(out) => match str::from_utf8(&out.source_code) {
Ok(v) => {
let lines: Vec<&str> = v.lines().collect();
assert!(lines.len() > line);
Some(lines[line].to_string())
},
_ => None,
},
_ => None,
}
}
}

/// This fetches source code, locally or remotely.
Expand Down
125 changes: 32 additions & 93 deletions cli/diagnostics.rs
Expand Up @@ -2,20 +2,13 @@
//! This module encodes TypeScript errors (diagnostics) into Rust structs and
//! contains code for printing them to the console.
use crate::ansi;
use crate::fmt_errors::format_maybe_source_line;
use crate::fmt_errors::format_maybe_source_name;
use crate::fmt_errors::DisplayFormatter;
use serde_json;
use serde_json::value::Value;
use std::fmt;

// 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, level: usize) -> String;
}

#[derive(Debug, PartialEq, Clone)]
pub struct Diagnostic {
pub items: Vec<DiagnosticItem>,
Expand Down Expand Up @@ -179,23 +172,21 @@ impl DiagnosticItem {
}
}

// TODO should chare logic with cli/js_errors, possibly with JSError
// implementing the `DisplayFormatter` trait.
impl DisplayFormatter for DiagnosticItem {
fn format_category_and_code(&self) -> String {
let category = match self.category {
DiagnosticCategory::Error => {
format!("- {}", ansi::red("error".to_string()))
format!("{}", ansi::red_bold("error".to_string()))
}
DiagnosticCategory::Warning => "- warn".to_string(),
DiagnosticCategory::Debug => "- debug".to_string(),
DiagnosticCategory::Info => "- info".to_string(),
DiagnosticCategory::Warning => "warn".to_string(),
DiagnosticCategory::Debug => "debug".to_string(),
DiagnosticCategory::Info => "info".to_string(),
_ => "".to_string(),
};

let code = ansi::grey(format!(" TS{}:", self.code.to_string())).to_string();
let code = ansi::bold(format!(" TS{}", self.code.to_string())).to_string();

format!("{}{} ", category, code)
format!("{}{}: ", category, code)
}

fn format_message(&self, level: usize) -> String {
Expand Down Expand Up @@ -229,85 +220,35 @@ impl DisplayFormatter for DiagnosticItem {
for related_diagnostic in related_information {
let rd = &related_diagnostic;
s.push_str(&format!(
"\n{}{}{}\n",
rd.format_source_name(2),
"\n{}\n\n ► {}{}\n",
rd.format_message(2),
rd.format_source_name(),
rd.format_source_line(4),
rd.format_message(4),
));
}

s
}

fn format_source_line(&self, level: usize) -> String {
if self.source_line.is_none() {
return "".to_string();
}

let source_line = self.source_line.as_ref().unwrap();
// sometimes source_line gets set with an empty string, which then outputs
// an empty source line when displayed, so need just short circuit here
if source_line.is_empty() {
return "".to_string();
}

assert!(self.line_number.is_some());
assert!(self.start_column.is_some());
assert!(self.end_column.is_some());
let line = (1 + self.line_number.unwrap()).to_string();
let line_color = ansi::black_on_white(line.to_string());
let line_len = line.clone().len();
let line_padding =
ansi::black_on_white(format!("{:indent$}", "", indent = line_len))
.to_string();
let mut s = String::new();
let start_column = self.start_column.unwrap();
let end_column = self.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 = match self.category {
DiagnosticCategory::Error => ansi::red(s).to_string(),
_ => ansi::cyan(s).to_string(),
};

let indent = format!("{:indent$}", "", indent = level);

format!(
"\n\n{}{} {}\n{}{} {}\n",
indent, line_color, source_line, indent, line_padding, color_underline
format_maybe_source_line(
self.source_line.clone(),
self.line_number,
self.start_column,
self.end_column,
match self.category {
DiagnosticCategory::Error => true,
_ => false,
},
level,
)
}

fn format_source_name(&self, level: usize) -> String {
if self.script_resource_name.is_none() {
return "".to_string();
}

let script_name = ansi::cyan(self.script_resource_name.clone().unwrap());
assert!(self.line_number.is_some());
assert!(self.start_column.is_some());
let line = ansi::yellow((1 + self.line_number.unwrap()).to_string());
let column = ansi::yellow((1 + self.start_column.unwrap()).to_string());
format!(
"{:indent$}{}:{}:{} ",
"",
script_name,
line,
column,
indent = level
fn format_source_name(&self) -> String {
format_maybe_source_name(
self.script_resource_name.clone(),
self.line_number,
self.start_column,
)
}
}
Expand All @@ -316,15 +257,13 @@ impl fmt::Display for DiagnosticItem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}{}{}{}{}",
self.format_source_name(0),
"{}{}\n\n► {}{}{}",
self.format_category_and_code(),
self.format_message(0),
self.format_source_name(),
self.format_source_line(0),
self.format_related_info(),
)?;

Ok(())
)
}
}

Expand Down Expand Up @@ -655,14 +594,14 @@ mod tests {
#[test]
fn diagnostic_to_string1() {
let d = diagnostic1();
let expected = "deno/tests/complex_diagnostics.ts:19:3 - error TS2322: Type \'(o: T) => { v: any; f: (x: B) => string; }[]\' is not assignable to type \'(r: B) => Value<B>[]\'.\n Types of parameters \'o\' and \'r\' are incompatible.\n Type \'B\' is not assignable to type \'T\'.\n\n19 values: o => [\n ~~~~~~\n\n deno/tests/complex_diagnostics.ts:7:3 \n\n 7 values?: (r: T) => Array<Value<T>>;\n ~~~~~~\n The expected type comes from property \'values\' which is declared here on type \'SettingsInterface<B>\'\n";
let expected = "error TS2322: Type \'(o: T) => { v: any; f: (x: B) => string; }[]\' is not assignable to type \'(r: B) => Value<B>[]\'.\n Types of parameters \'o\' and \'r\' are incompatible.\n Type \'B\' is not assignable to type \'T\'.\n\n► deno/tests/complex_diagnostics.ts:19:3\n\n19 values: o => [\n ~~~~~~\n\n The expected type comes from property \'values\' which is declared here on type \'SettingsInterface<B>\'\n\ndeno/tests/complex_diagnostics.ts:7:3\n\n 7 values?: (r: T) => Array<Value<T>>;\n ~~~~~~\n\n";
assert_eq!(expected, strip_ansi_codes(&d.to_string()));
}

#[test]
fn diagnostic_to_string2() {
let d = diagnostic2();
let expected = "deno/tests/complex_diagnostics.ts:19:3 - error TS2322: Example 1\n\n19 values: o => [\n ~~~~~~\n\n/foo/bar.ts:129:3 - error TS2000: Example 2\n\n129 values: undefined,\n ~~~~~~\n\n\nFound 2 errors.\n";
let expected = "error TS2322: Example 1\n\ndeno/tests/complex_diagnostics.ts:19:3\n\n19 values: o => [\n ~~~~~~\n\nerror TS2000: Example 2\n\n/foo/bar.ts:129:3\n\n129 values: undefined,\n ~~~~~~\n\n\nFound 2 errors.\n";
assert_eq!(expected, strip_ansi_codes(&d.to_string()));
}
}
86 changes: 5 additions & 81 deletions cli/errors.rs
@@ -1,13 +1,12 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use crate::ansi;
use crate::diagnostics;
use crate::fmt_errors::JSErrorColor;
use crate::import_map;
pub use crate::msg::ErrorKind;
use crate::resolve_addr::ResolveAddrError;
use crate::source_maps::apply_source_map;
use crate::source_maps::SourceMapGetter;
use deno::JSError;
use deno::StackFrame;
use hyper;
#[cfg(unix)]
use nix::{errno::Errno, Error as UnixError};
Expand Down Expand Up @@ -35,84 +34,6 @@ enum Repr {
JSError(JSError),
}

/// Wrapper around JSError which provides color to_string.
struct JSErrorColor<'a>(pub &'a JSError);

impl<'a> fmt::Display for JSErrorColor<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let e = self.0;
if e.script_resource_name.is_some() {
let script_resource_name = e.script_resource_name.as_ref().unwrap();
// Avoid showing internal code from gen/cli/bundle/main.js
if script_resource_name != "gen/cli/bundle/main.js"
&& script_resource_name != "gen/cli/bundle/compiler.js"
{
if e.line_number.is_some() && e.start_column.is_some() {
assert!(e.line_number.is_some());
assert!(e.start_column.is_some());
let script_line_column = format_script_line_column(
script_resource_name,
e.line_number.unwrap() - 1,
e.start_column.unwrap() - 1,
);
write!(f, "{}", script_line_column)?;
}
if e.source_line.is_some() {
write!(f, "\n{}\n", e.source_line.as_ref().unwrap())?;
let mut s = String::new();
for i in 0..e.end_column.unwrap() {
if i >= e.start_column.unwrap() {
s.push('^');
} else {
s.push(' ');
}
}
writeln!(f, "{}", ansi::red_bold(s))?;
}
}
}

write!(f, "{}", ansi::bold(e.message.clone()))?;

for frame in &e.frames {
write!(f, "\n{}", StackFrameColor(&frame).to_string())?;
}
Ok(())
}
}

struct StackFrameColor<'a>(&'a StackFrame);

impl<'a> fmt::Display for StackFrameColor<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let frame = self.0;
// Note when we print to string, we change from 0-indexed to 1-indexed.
let function_name = ansi::italic_bold(frame.function_name.clone());
let script_line_column =
format_script_line_column(&frame.script_name, frame.line, frame.column);

if !frame.function_name.is_empty() {
write!(f, " at {} ({})", function_name, script_line_column)
} else if frame.is_eval {
write!(f, " at eval ({})", script_line_column)
} else {
write!(f, " at {}", script_line_column)
}
}
}

fn format_script_line_column(
script_name: &str,
line: i64,
column: i64,
) -> String {
// TODO match this style with how typescript displays errors.
let line = ansi::yellow((1 + line).to_string());
let column = ansi::yellow((1 + column).to_string());
let script_name = ansi::cyan(script_name.to_string());
format!("{}:{}:{}", script_name, line, column)
}

/// Create a new simple DenoError.
pub fn new(kind: ErrorKind, msg: String) -> DenoError {
DenoError {
Expand Down Expand Up @@ -347,7 +268,10 @@ pub fn permission_denied() -> DenoError {
}

pub fn op_not_implemented() -> DenoError {
new(ErrorKind::OpNotAvailable, String::from("op not implemented"))
new(
ErrorKind::OpNotAvailable,
String::from("op not implemented"),
)
}

pub fn worker_init_failed() -> DenoError {
Expand Down

0 comments on commit 663218d

Please sign in to comment.