Color exceptions (#1698)

This commit is contained in:
Ryan Dahl 2019-02-07 20:07:20 -05:00 committed by GitHub
parent f22e0d72c5
commit 46804e50ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 159 additions and 47 deletions

View file

@ -31,6 +31,7 @@ main_extern = [
"$rust_build:libc",
"$rust_build:log",
"$rust_build:rand",
"$rust_build:regex",
"$rust_build:remove_dir_all",
"$rust_build:ring",
"$rust_build:rustyline",

69
src/ansi.rs Normal file
View file

@ -0,0 +1,69 @@
use ansi_term::Color::Cyan;
use ansi_term::Color::Red;
use ansi_term::Color::Yellow;
use ansi_term::Style;
use regex::Regex;
use std::borrow::Cow;
use std::env;
use std::fmt;
lazy_static! {
// STRIP_ANSI_RE and strip_ansi_codes are lifted from the "console" crate.
// Copyright 2017 Armin Ronacher <armin.ronacher@active-4.com>. MIT License.
static ref STRIP_ANSI_RE: Regex = Regex::new(
r"[\x1b\x9b][\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]"
).unwrap();
static ref NO_COLOR: bool = {
env::var_os("NO_COLOR").is_some()
};
}
/// Helper function to strip ansi codes.
#[allow(dead_code)]
pub fn strip_ansi_codes(s: &str) -> Cow<str> {
STRIP_ANSI_RE.replace_all(s, "")
}
pub fn use_color() -> bool {
*NO_COLOR == false
}
pub fn red_bold(s: String) -> impl fmt::Display {
let mut style = Style::new();
if use_color() {
style = style.bold().fg(Red);
}
style.paint(s)
}
pub fn italic_bold(s: String) -> impl fmt::Display {
let mut style = Style::new();
if use_color() {
style = style.italic().bold();
}
style.paint(s)
}
pub fn yellow(s: String) -> impl fmt::Display {
let mut style = Style::new();
if use_color() {
style = style.fg(Yellow);
}
style.paint(s)
}
pub fn cyan(s: String) -> impl fmt::Display {
let mut style = Style::new();
if use_color() {
style = style.fg(Cyan);
}
style.paint(s)
}
pub fn bold(s: String) -> impl fmt::Display {
let mut style = Style::new();
if use_color() {
style = style.bold();
}
style.paint(s)
}

View file

@ -9,6 +9,7 @@
// console.log(err.stack);
// It would require calling into Rust from Error.prototype.prepareStackTrace.
use crate::ansi;
use serde_json;
use source_map_mappings::parse_mappings;
use source_map_mappings::Bias;
@ -32,8 +33,8 @@ type CachedMaps = HashMap<String, Option<SourceMap>>;
#[derive(Debug, PartialEq)]
pub struct StackFrame {
pub line: u32, // zero indexed
pub column: u32, // zero indexed
pub line: i64, // zero indexed
pub column: i64, // zero indexed
pub script_name: String,
pub function_name: String,
pub is_eval: bool,
@ -57,46 +58,69 @@ pub struct JSError {
pub frames: Vec<StackFrame>,
}
impl ToString for StackFrame {
fn to_string(&self) -> String {
impl fmt::Display for StackFrame {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Note when we print to string, we change from 0-indexed to 1-indexed.
let (line, column) = (self.line + 1, self.column + 1);
let function_name = ansi::italic_bold(self.function_name.clone());
let script_line_column =
format_script_line_column(&self.script_name, self.line, self.column);
if !self.function_name.is_empty() {
format!(
" at {} ({}:{}:{})",
self.function_name, self.script_name, line, column
)
write!(f, " at {} ({})", function_name, script_line_column)
} else if self.is_eval {
format!(" at eval ({}:{}:{})", self.script_name, line, column)
write!(f, " at eval ({})", script_line_column)
} else {
format!(" at {}:{}:{}", self.script_name, line, column)
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)
}
impl fmt::Display for JSError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.script_resource_name.is_some() {
let script_resource_name = self.script_resource_name.as_ref().unwrap();
// Avoid showing internal code from gen/bundle/main.js
if script_resource_name != "gen/bundle/main.js" {
write!(f, "{}", script_resource_name)?;
if self.line_number.is_some() {
write!(
f,
":{}:{}",
self.line_number.unwrap(),
self.start_column.unwrap()
)?;
if script_resource_name != "gen/bundle/main.js"
&& script_resource_name != "gen/bundle/compiler.js"
{
if self.line_number.is_some() && self.start_column.is_some() {
assert!(self.line_number.is_some());
assert!(self.start_column.is_some());
let script_line_column = format_script_line_column(
script_resource_name,
self.line_number.unwrap() - 1,
self.start_column.unwrap() - 1,
);
write!(f, "{}", script_line_column)?;
}
if self.source_line.is_some() {
write!(f, "\n{}\n\n", self.source_line.as_ref().unwrap())?;
write!(f, "\n{}\n", self.source_line.as_ref().unwrap())?;
let mut s = String::new();
for i in 0..self.end_column.unwrap() {
if i >= self.start_column.unwrap() {
s.push('^');
} else {
s.push(' ');
}
}
write!(f, "{}\n", ansi::red_bold(s))?;
}
}
}
write!(f, "{}", &self.message)?;
write!(f, "{}", ansi::bold(self.message.clone()))?;
for frame in &self.frames {
write!(f, "\n{}", &frame.to_string())?;
@ -117,13 +141,13 @@ impl StackFrame {
if !line_v.is_u64() {
return None;
}
let line = line_v.as_u64().unwrap() as u32;
let line = line_v.as_u64().unwrap() as i64;
let column_v = &obj["column"];
if !column_v.is_u64() {
return None;
}
let column = column_v.as_u64().unwrap() as u32;
let column = column_v.as_u64().unwrap() as i64;
let script_name_v = &obj["scriptName"];
if !script_name_v.is_string() {
@ -184,12 +208,16 @@ impl StackFrame {
) -> StackFrame {
let maybe_sm =
get_mappings(self.script_name.as_ref(), mappings_map, getter);
let frame_pos = (self.script_name.to_owned(), self.line, self.column);
let frame_pos = (
self.script_name.to_owned(),
self.line as i64,
self.column as i64,
);
let (script_name, line, column) = match maybe_sm {
None => frame_pos,
Some(sm) => match sm.mappings.original_location_for(
self.line,
self.column,
self.line as u32,
self.column as u32,
Bias::default(),
) {
None => frame_pos,
@ -199,8 +227,8 @@ impl StackFrame {
let orig_source = sm.sources[original.source as usize].clone();
(
orig_source,
original.original_line,
original.original_column,
original.original_line as i64,
original.original_column as i64,
)
}
},
@ -379,6 +407,7 @@ fn get_mappings<'a>(
#[cfg(test)]
mod tests {
use super::*;
use crate::ansi::strip_ansi_codes;
fn error1() -> JSError {
JSError {
@ -546,14 +575,20 @@ mod tests {
#[test]
fn stack_frame_to_string() {
let e = error1();
assert_eq!(" at foo (foo_bar.ts:5:17)", e.frames[0].to_string());
assert_eq!(" at qat (bar_baz.ts:6:21)", e.frames[1].to_string());
assert_eq!(
" at foo (foo_bar.ts:5:17)",
strip_ansi_codes(&e.frames[0].to_string())
);
assert_eq!(
" at qat (bar_baz.ts:6:21)",
strip_ansi_codes(&e.frames[1].to_string())
);
}
#[test]
fn js_error_to_string() {
let e = error1();
assert_eq!("Error: foo bar\n at foo (foo_bar.ts:5:17)\n at qat (bar_baz.ts:6:21)\n at deno_main.js:2:2", e.to_string());
assert_eq!("Error: foo bar\n at foo (foo_bar.ts:5:17)\n at qat (bar_baz.ts:6:21)\n at deno_main.js:2:2", strip_ansi_codes(&e.to_string()));
}
#[test]

View file

@ -8,6 +8,7 @@ extern crate futures;
#[macro_use]
extern crate serde_json;
mod ansi;
pub mod compiler;
pub mod deno_dir;
pub mod errors;

View file

@ -3,7 +3,7 @@ before error
world
[WILDCARD]tests/async_error.ts:4:10
throw Error("error");
^
Uncaught Error: error
at foo ([WILDCARD]tests/async_error.ts:4:9)
at [WILDCARD]tests/async_error.ts:7:1

View file

@ -1,10 +1,10 @@
[WILDCARD]tests/error_003_typescript.tsILDCARD] - error TS2552: Cannot find name 'consol'. Did you mean 'console'?
[WILDCARD]tests/error_003_typescript.ts[WILDCARD] - error TS2552: Cannot find name 'consol'. Did you mean 'console'?
[WILDCARD] consol.log("hello world!");
[WILDCARD]~~~~~~
[WILDCARD]~~~~~~
$asset$/lib.deno_runtime.d.tsILDCARD]
$asset$/lib.deno_runtime.d.ts[WILDCARD]
[WILDCARD]declare const console: consoleTypes.Console;
[WILDCARD]~~~~~~~
[WILDCARD]~~~~~~~
[WILDCARD]'console' is declared here.

View file

@ -1,5 +1,4 @@
Compiling [WILDCARD]tests/error_004_missing_module.ts
[WILDCARD]
Uncaught NotFound: Cannot resolve module "bad-module.ts" from "[WILDCARD]/tests/error_004_missing_module.ts"
at DenoError ([WILDCARD]/js/errors.ts:[WILDCARD])
at maybeError ([WILDCARD]/js/errors.ts:[WILDCARD])

View file

@ -1,5 +1,4 @@
Compiling [WILDCARD]tests/error_006_import_ext_failure.ts
[WILDCARD]
Uncaught NotFound: Cannot resolve module "./non-existent" from "[WILDCARD]/tests/error_006_import_ext_failure.ts"
at DenoError ([WILDCARD]/js/errors.ts:[WILDCARD])
at maybeError ([WILDCARD]/js/errors.ts:[WILDCARD])

View file

@ -1,5 +1,5 @@
[WILDCARD]tests/error_008_checkjs.js:2:0
consol.log("hello world!");
^
Uncaught ReferenceError: consol is not defined
at [WILDCARD]tests/error_008_checkjs.js:2:1

View file

@ -1,4 +1,4 @@
[WILDCARD]tests/error_syntax.js:3:5
(the following is a syntax error ^^ ! )
^^^^^^^^^
Uncaught SyntaxError: Unexpected identifier

View file

@ -16,6 +16,12 @@ import argparse
from util import root_path, tests_path, pattern_match, \
green_ok, red_failed, rmtree, executable_suffix
def strip_ansi_codes(s):
ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
return ansi_escape.sub('', s)
def read_test(file_name):
with open(file_name, "r") as f:
test_file = f.read()
@ -51,20 +57,20 @@ def integration_tests(deno_exe, test_filter = None):
continue
test_abs = os.path.join(tests_path, test_filename)
print "read_test", test_abs
test = read_test(test_abs)
exit_code = int(test.get("exit_code", 0))
args = test.get("args", "").split(" ")
check_stderr = str2bool(test.get("check_stderr", "false"))
stderr = subprocess.STDOUT if check_stderr else None
stderr = subprocess.STDOUT if check_stderr else open(os.devnull, 'w')
output_abs = os.path.join(root_path, test.get("output", ""))
with open(output_abs, 'r') as f:
expected_out = f.read()
cmd = [deno_exe] + args
print "test %s" % (test_filename)
print " ".join(cmd)
sys.stdout.write("tests/%s ... " % (test_filename))
sys.stdout.flush()
actual_code = 0
try:
actual_out = subprocess.check_output(
@ -80,14 +86,16 @@ def integration_tests(deno_exe, test_filter = None):
print actual_out
sys.exit(1)
actual_out = strip_ansi_codes(actual_out)
if pattern_match(expected_out, actual_out) != True:
print "... " + red_failed()
print red_failed()
print "Expected output does not match actual."
print "Expected output: \n" + expected_out
print "Actual output: \n" + actual_out
sys.exit(1)
print "... " + green_ok()
print green_ok()
def main():
parser = argparse.ArgumentParser()