mirror of
https://github.com/denoland/deno.git
synced 2024-12-23 15:49:44 -05:00
Color exceptions (#1698)
This commit is contained in:
parent
f22e0d72c5
commit
46804e50ed
11 changed files with 159 additions and 47 deletions
1
BUILD.gn
1
BUILD.gn
|
@ -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
69
src/ansi.rs
Normal 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)
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
[96m[WILDCARD]tests/error_003_typescript.ts[WILDCARD] - [91merror[0m[90m TS2552: [0mCannot 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]~~~~~~[0m
|
||||
[WILDCARD]~~~~~~
|
||||
|
||||
[96m$asset$/lib.deno_runtime.d.ts[WILDCARD]
|
||||
$asset$/lib.deno_runtime.d.ts[WILDCARD]
|
||||
[WILDCARD]declare const console: consoleTypes.Console;
|
||||
[WILDCARD]~~~~~~~[0m
|
||||
[WILDCARD]~~~~~~~
|
||||
[WILDCARD]'console' is declared here.
|
||||
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[WILDCARD]tests/error_syntax.js:3:5
|
||||
(the following is a syntax error ^^ ! )
|
||||
|
||||
^^^^^^^^^
|
||||
Uncaught SyntaxError: Unexpected identifier
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue