diff options
Diffstat (limited to 'cli')
| -rw-r--r-- | cli/ansi.rs | 8 | ||||
| -rw-r--r-- | cli/compiler.rs | 27 | ||||
| -rw-r--r-- | cli/deno_dir.rs | 30 | ||||
| -rw-r--r-- | cli/deno_error.rs | 538 | ||||
| -rw-r--r-- | cli/diagnostics.rs | 125 | ||||
| -rw-r--r-- | cli/dispatch_minimal.rs | 16 | ||||
| -rw-r--r-- | cli/errors.rs | 295 | ||||
| -rw-r--r-- | cli/fmt_errors.rs | 289 | ||||
| -rw-r--r-- | cli/fs.rs | 8 | ||||
| -rw-r--r-- | cli/http_util.rs | 14 | ||||
| -rw-r--r-- | cli/main.rs | 19 | ||||
| -rw-r--r-- | cli/msg.fbs | 6 | ||||
| -rw-r--r-- | cli/msg_util.rs | 6 | ||||
| -rw-r--r-- | cli/ops.rs | 101 | ||||
| -rw-r--r-- | cli/permissions.rs | 4 | ||||
| -rw-r--r-- | cli/repl.rs | 4 | ||||
| -rw-r--r-- | cli/resources.rs | 26 | ||||
| -rw-r--r-- | cli/signal.rs | 4 | ||||
| -rw-r--r-- | cli/source_maps.rs (renamed from cli/js_errors.rs) | 391 | ||||
| -rw-r--r-- | cli/state.rs | 4 | ||||
| -rw-r--r-- | cli/worker.rs | 52 |
21 files changed, 1243 insertions, 724 deletions
diff --git a/cli/ansi.rs b/cli/ansi.rs index b9e9fe123..32936ace0 100644 --- a/cli/ansi.rs +++ b/cli/ansi.rs @@ -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() { diff --git a/cli/compiler.rs b/cli/compiler.rs index de5b5c609..b6fb0375c 100644 --- a/cli/compiler.rs +++ b/cli/compiler.rs @@ -1,4 +1,6 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use crate::deno_error::err_check; +use crate::deno_error::DenoError; use crate::diagnostics::Diagnostic; use crate::msg; use crate::resources; @@ -6,7 +8,6 @@ use crate::startup_data; use crate::state::*; use crate::tokio_util; use crate::worker::Worker; -use deno::js_check; use deno::Buf; use futures::Future; use futures::Stream; @@ -92,7 +93,7 @@ pub fn bundle_async( state: ThreadSafeState, module_name: String, out_file: String, -) -> impl Future<Item = (), Error = Diagnostic> { +) -> impl Future<Item = (), Error = DenoError> { debug!( "Invoking the compiler to bundle. module_name: {}", module_name @@ -112,9 +113,9 @@ pub fn bundle_async( // as was done previously. state.clone(), ); - js_check(worker.execute("denoMain()")); - js_check(worker.execute("workerMain()")); - js_check(worker.execute("compilerMain()")); + err_check(worker.execute("denoMain()")); + err_check(worker.execute("workerMain()")); + err_check(worker.execute("compilerMain()")); let resource = worker.state.resource.clone(); let compiler_rid = resource.rid; @@ -140,7 +141,7 @@ pub fn bundle_async( let json_str = std::str::from_utf8(&msg).unwrap(); debug!("Message: {}", json_str); if let Some(diagnostics) = Diagnostic::from_emit_result(json_str) { - return Err(diagnostics); + return Err(DenoError::from(diagnostics)); } } @@ -152,7 +153,7 @@ pub fn bundle_async( pub fn compile_async( state: ThreadSafeState, module_meta_data: &ModuleMetaData, -) -> impl Future<Item = ModuleMetaData, Error = Diagnostic> { +) -> impl Future<Item = ModuleMetaData, Error = DenoError> { let module_name = module_meta_data.module_name.clone(); debug!( @@ -174,9 +175,9 @@ pub fn compile_async( // as was done previously. state.clone(), ); - js_check(worker.execute("denoMain()")); - js_check(worker.execute("workerMain()")); - js_check(worker.execute("compilerMain()")); + err_check(worker.execute("denoMain()")); + err_check(worker.execute("workerMain()")); + err_check(worker.execute("compilerMain()")); let compiling_job = state.progress.add(format!("Compiling {}", module_name)); @@ -205,7 +206,7 @@ pub fn compile_async( let json_str = std::str::from_utf8(&msg).unwrap(); debug!("Message: {}", json_str); if let Some(diagnostics) = Diagnostic::from_emit_result(json_str) { - return Err(diagnostics); + return Err(DenoError::from(diagnostics)); } } @@ -235,7 +236,7 @@ pub fn compile_async( pub fn compile_sync( state: ThreadSafeState, module_meta_data: &ModuleMetaData, -) -> Result<ModuleMetaData, Diagnostic> { +) -> Result<ModuleMetaData, DenoError> { tokio_util::block_on(compile_async(state, module_meta_data)) } @@ -306,6 +307,6 @@ mod tests { ]); let out = bundle_async(state, module_name, String::from("$deno$/bundle.js")); - assert_eq!(tokio_util::block_on(out), Ok(())); + assert!(tokio_util::block_on(out).is_ok()); } } diff --git a/cli/deno_dir.rs b/cli/deno_dir.rs index c0ee051b8..ba0217bbe 100644 --- a/cli/deno_dir.rs +++ b/cli/deno_dir.rs @@ -1,14 +1,14 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. use crate::compiler::ModuleMetaData; -use crate::errors; -use crate::errors::DenoError; -use crate::errors::DenoResult; -use crate::errors::ErrorKind; +use crate::deno_error; +use crate::deno_error::DenoError; +use crate::deno_error::DenoResult; +use crate::deno_error::ErrorKind; use crate::fs as deno_fs; use crate::http_util; -use crate::js_errors::SourceMapGetter; use crate::msg; use crate::progress::Progress; +use crate::source_maps::SourceMapGetter; use crate::tokio_util; use crate::version; use dirs; @@ -152,7 +152,7 @@ impl DenoDir { referrer: &str, use_cache: bool, no_fetch: bool, - ) -> impl Future<Item = ModuleMetaData, Error = errors::DenoError> { + ) -> impl Future<Item = ModuleMetaData, Error = deno_error::DenoError> { debug!( "fetch_module_meta_data. specifier {} referrer {}", specifier, referrer @@ -187,7 +187,7 @@ impl DenoDir { Err(err) => { if err.kind() == ErrorKind::NotFound { // For NotFound, change the message to something better. - return Err(errors::new( + return Err(deno_error::new( ErrorKind::NotFound, format!( "Cannot resolve module \"{}\" from \"{}\"", @@ -255,7 +255,7 @@ impl DenoDir { referrer: &str, use_cache: bool, no_fetch: bool, - ) -> Result<ModuleMetaData, errors::DenoError> { + ) -> Result<ModuleMetaData, deno_error::DenoError> { tokio_util::block_on( self .fetch_module_meta_data_async(specifier, referrer, use_cache, no_fetch), @@ -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. diff --git a/cli/deno_error.rs b/cli/deno_error.rs new file mode 100644 index 000000000..6e14f3902 --- /dev/null +++ b/cli/deno_error.rs @@ -0,0 +1,538 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +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 hyper; +#[cfg(unix)] +use nix::{errno::Errno, Error as UnixError}; +use std; +use std::fmt; +use std::io; +use std::str; +use url; + +pub type DenoResult<T> = std::result::Result<T, DenoError>; + +#[derive(Debug)] +pub struct DenoError { + repr: Repr, +} + +#[derive(Debug)] +enum Repr { + Simple(ErrorKind, String), + IoErr(io::Error), + UrlErr(url::ParseError), + HyperErr(hyper::Error), + ImportMapErr(import_map::ImportMapError), + Diagnostic(diagnostics::Diagnostic), + JSError(JSError), +} + +/// Create a new simple DenoError. +pub fn new(kind: ErrorKind, msg: String) -> DenoError { + DenoError { + repr: Repr::Simple(kind, msg), + } +} + +impl DenoError { + pub fn kind(&self) -> ErrorKind { + match self.repr { + Repr::Simple(kind, ref _msg) => kind, + // Repr::Simple(kind) => kind, + Repr::IoErr(ref err) => { + use std::io::ErrorKind::*; + match err.kind() { + NotFound => ErrorKind::NotFound, + PermissionDenied => ErrorKind::PermissionDenied, + ConnectionRefused => ErrorKind::ConnectionRefused, + ConnectionReset => ErrorKind::ConnectionReset, + ConnectionAborted => ErrorKind::ConnectionAborted, + NotConnected => ErrorKind::NotConnected, + AddrInUse => ErrorKind::AddrInUse, + AddrNotAvailable => ErrorKind::AddrNotAvailable, + BrokenPipe => ErrorKind::BrokenPipe, + AlreadyExists => ErrorKind::AlreadyExists, + WouldBlock => ErrorKind::WouldBlock, + InvalidInput => ErrorKind::InvalidInput, + InvalidData => ErrorKind::InvalidData, + TimedOut => ErrorKind::TimedOut, + Interrupted => ErrorKind::Interrupted, + WriteZero => ErrorKind::WriteZero, + Other => ErrorKind::Other, + UnexpectedEof => ErrorKind::UnexpectedEof, + _ => unreachable!(), + } + } + Repr::UrlErr(ref err) => { + use url::ParseError::*; + match err { + EmptyHost => ErrorKind::EmptyHost, + IdnaError => ErrorKind::IdnaError, + InvalidPort => ErrorKind::InvalidPort, + InvalidIpv4Address => ErrorKind::InvalidIpv4Address, + InvalidIpv6Address => ErrorKind::InvalidIpv6Address, + InvalidDomainCharacter => ErrorKind::InvalidDomainCharacter, + RelativeUrlWithoutBase => ErrorKind::RelativeUrlWithoutBase, + RelativeUrlWithCannotBeABaseBase => { + ErrorKind::RelativeUrlWithCannotBeABaseBase + } + SetHostOnCannotBeABaseUrl => ErrorKind::SetHostOnCannotBeABaseUrl, + Overflow => ErrorKind::Overflow, + } + } + Repr::HyperErr(ref err) => { + // For some reason hyper::errors::Kind is private. + if err.is_parse() { + ErrorKind::HttpParse + } else if err.is_user() { + ErrorKind::HttpUser + } else if err.is_canceled() { + ErrorKind::HttpCanceled + } else if err.is_closed() { + ErrorKind::HttpClosed + } else { + ErrorKind::HttpOther + } + } + Repr::ImportMapErr(ref _err) => ErrorKind::ImportMapError, + Repr::Diagnostic(ref _err) => ErrorKind::Diagnostic, + Repr::JSError(ref _err) => ErrorKind::JSError, + } + } + + pub fn apply_source_map<G: SourceMapGetter>(self, getter: &G) -> Self { + if let Repr::JSError(js_error) = self.repr { + return DenoError { + repr: Repr::JSError(apply_source_map(&js_error, getter)), + }; + } else { + panic!("attempt to apply source map an unremappable error") + } + } +} + +impl fmt::Display for DenoError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.repr { + Repr::Simple(_kind, ref err_str) => f.pad(err_str), + Repr::IoErr(ref err) => err.fmt(f), + Repr::UrlErr(ref err) => err.fmt(f), + Repr::HyperErr(ref err) => err.fmt(f), + Repr::ImportMapErr(ref err) => f.pad(&err.msg), + Repr::Diagnostic(ref err) => err.fmt(f), + Repr::JSError(ref err) => JSErrorColor(err).fmt(f), + } + } +} + +impl std::error::Error for DenoError { + fn description(&self) -> &str { + match self.repr { + Repr::Simple(_kind, ref msg) => msg.as_str(), + Repr::IoErr(ref err) => err.description(), + Repr::UrlErr(ref err) => err.description(), + Repr::HyperErr(ref err) => err.description(), + Repr::ImportMapErr(ref err) => &err.msg, + Repr::Diagnostic(ref err) => &err.items[0].message, + Repr::JSError(ref err) => &err.description(), + } + } + + fn cause(&self) -> Option<&dyn std::error::Error> { + match self.repr { + Repr::Simple(_kind, ref _msg) => None, + Repr::IoErr(ref err) => Some(err), + Repr::UrlErr(ref err) => Some(err), + Repr::HyperErr(ref err) => Some(err), + Repr::ImportMapErr(ref _err) => None, + Repr::Diagnostic(ref _err) => None, + Repr::JSError(ref err) => Some(err), + } + } +} + +impl From<io::Error> for DenoError { + #[inline] + fn from(err: io::Error) -> Self { + Self { + repr: Repr::IoErr(err), + } + } +} + +impl From<url::ParseError> for DenoError { + #[inline] + fn from(err: url::ParseError) -> Self { + Self { + repr: Repr::UrlErr(err), + } + } +} + +impl From<hyper::Error> for DenoError { + #[inline] + fn from(err: hyper::Error) -> Self { + Self { + repr: Repr::HyperErr(err), + } + } +} + +impl From<ResolveAddrError> for DenoError { + fn from(e: ResolveAddrError) -> Self { + match e { + ResolveAddrError::Syntax => Self { + repr: Repr::Simple( + ErrorKind::InvalidInput, + "invalid address syntax".to_string(), + ), + }, + ResolveAddrError::Resolution(io_err) => Self { + repr: Repr::IoErr(io_err), + }, + } + } +} + +#[cfg(unix)] +impl From<UnixError> for DenoError { + fn from(e: UnixError) -> Self { + match e { + UnixError::Sys(Errno::EPERM) => Self { + repr: Repr::Simple( + ErrorKind::PermissionDenied, + Errno::EPERM.desc().to_owned(), + ), + }, + UnixError::Sys(Errno::EINVAL) => Self { + repr: Repr::Simple( + ErrorKind::InvalidInput, + Errno::EINVAL.desc().to_owned(), + ), + }, + UnixError::Sys(Errno::ENOENT) => Self { + repr: Repr::Simple( + ErrorKind::NotFound, + Errno::ENOENT.desc().to_owned(), + ), + }, + UnixError::Sys(err) => Self { + repr: Repr::Simple(ErrorKind::UnixError, err.desc().to_owned()), + }, + _ => Self { + repr: Repr::Simple(ErrorKind::Other, format!("{}", e)), + }, + } + } +} + +impl From<import_map::ImportMapError> for DenoError { + fn from(err: import_map::ImportMapError) -> Self { + Self { + repr: Repr::ImportMapErr(err), + } + } +} + +impl From<diagnostics::Diagnostic> for DenoError { + fn from(diagnostic: diagnostics::Diagnostic) -> Self { + Self { + repr: Repr::Diagnostic(diagnostic), + } + } +} + +impl From<JSError> for DenoError { + fn from(err: JSError) -> Self { + Self { + repr: Repr::JSError(err), + } + } +} + +pub fn bad_resource() -> DenoError { + new(ErrorKind::BadResource, String::from("bad resource id")) +} + +pub fn permission_denied() -> DenoError { + new( + ErrorKind::PermissionDenied, + String::from("permission denied"), + ) +} + +pub fn op_not_implemented() -> DenoError { + new( + ErrorKind::OpNotAvailable, + String::from("op not implemented"), + ) +} + +pub fn worker_init_failed() -> DenoError { + // TODO(afinch7) pass worker error data through here + new( + ErrorKind::WorkerInitFailed, + String::from("worker init failed"), + ) +} + +pub fn no_buffer_specified() -> DenoError { + new(ErrorKind::InvalidInput, String::from("no buffer specified")) +} + +pub fn no_async_support() -> DenoError { + new( + ErrorKind::NoAsyncSupport, + String::from("op doesn't support async calls"), + ) +} + +pub fn no_sync_support() -> DenoError { + new( + ErrorKind::NoSyncSupport, + String::from("op doesn't support sync calls"), + ) +} + +pub fn err_check<R>(r: Result<R, DenoError>) { + if let Err(e) = r { + panic!(e.to_string()); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ansi::strip_ansi_codes; + use crate::diagnostics::Diagnostic; + use crate::diagnostics::DiagnosticCategory; + use crate::diagnostics::DiagnosticItem; + use crate::import_map::ImportMapError; + use deno::StackFrame; + + fn js_error() -> JSError { + JSError { + message: "Error: foo bar".to_string(), + source_line: None, + script_resource_name: None, + line_number: None, + start_position: None, + end_position: None, + error_level: None, + start_column: None, + end_column: None, + frames: vec![ + StackFrame { + line: 4, + column: 16, + script_name: "foo_bar.ts".to_string(), + function_name: "foo".to_string(), + is_eval: false, + is_constructor: false, + is_wasm: false, + }, + StackFrame { + line: 5, + column: 20, + script_name: "bar_baz.ts".to_string(), + function_name: "qat".to_string(), + is_eval: false, + is_constructor: false, + is_wasm: false, + }, + StackFrame { + line: 1, + column: 1, + script_name: "deno_main.js".to_string(), + function_name: "".to_string(), + is_eval: false, + is_constructor: false, + is_wasm: false, + }, + ], + } + } + + fn diagnostic() -> 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, + }, + ], + } + } + + struct MockSourceMapGetter {} + + impl SourceMapGetter for MockSourceMapGetter { + fn get_source_map(&self, _script_name: &str) -> Option<Vec<u8>> { + Some(vec![]) + } + + fn get_source_line( + &self, + _script_name: &str, + _line: usize, + ) -> Option<String> { + None + } + } + + fn io_error() -> io::Error { + io::Error::from(io::ErrorKind::NotFound) + } + + fn url_error() -> url::ParseError { + url::ParseError::EmptyHost + } + + fn import_map_error() -> ImportMapError { + ImportMapError { + msg: "an import map error".to_string(), + } + } + + #[test] + fn test_simple_error() { + let err = new(ErrorKind::NoError, "foo".to_string()); + assert_eq!(err.kind(), ErrorKind::NoError); + assert_eq!(err.to_string(), "foo"); + } + + #[test] + fn test_io_error() { + let err = DenoError::from(io_error()); + assert_eq!(err.kind(), ErrorKind::NotFound); + assert_eq!(err.to_string(), "entity not found"); + } + + #[test] + fn test_url_error() { + let err = DenoError::from(url_error()); + assert_eq!(err.kind(), ErrorKind::EmptyHost); + assert_eq!(err.to_string(), "empty host"); + } + + // TODO find a way to easily test tokio errors and unix errors + + #[test] + fn test_diagnostic() { + let err = DenoError::from(diagnostic()); + assert_eq!(err.kind(), ErrorKind::Diagnostic); + assert_eq!(strip_ansi_codes(&err.to_string()), "error TS2322: Example 1\n\n► deno/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"); + } + + #[test] + fn test_js_error() { + let err = DenoError::from(js_error()); + assert_eq!(err.kind(), ErrorKind::JSError); + assert_eq!(strip_ansi_codes(&err.to_string()), "error: 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"); + } + + #[test] + fn test_import_map_error() { + let err = DenoError::from(import_map_error()); + assert_eq!(err.kind(), ErrorKind::ImportMapError); + assert_eq!(err.to_string(), "an import map error"); + } + + #[test] + fn test_bad_resource() { + let err = bad_resource(); + assert_eq!(err.kind(), ErrorKind::BadResource); + assert_eq!(err.to_string(), "bad resource id"); + } + + #[test] + fn test_permission_denied() { + let err = permission_denied(); + assert_eq!(err.kind(), ErrorKind::PermissionDenied); + assert_eq!(err.to_string(), "permission denied"); + } + + #[test] + fn test_op_not_implemented() { + let err = op_not_implemented(); + assert_eq!(err.kind(), ErrorKind::OpNotAvailable); + assert_eq!(err.to_string(), "op not implemented"); + } + + #[test] + fn test_worker_init_failed() { + let err = worker_init_failed(); + assert_eq!(err.kind(), ErrorKind::WorkerInitFailed); + assert_eq!(err.to_string(), "worker init failed"); + } + + #[test] + fn test_no_buffer_specified() { + let err = no_buffer_specified(); + assert_eq!(err.kind(), ErrorKind::InvalidInput); + assert_eq!(err.to_string(), "no buffer specified"); + } + + #[test] + fn test_no_async_support() { + let err = no_async_support(); + assert_eq!(err.kind(), ErrorKind::NoAsyncSupport); + assert_eq!(err.to_string(), "op doesn't support async calls"); + } + + #[test] + fn test_no_sync_support() { + let err = no_sync_support(); + assert_eq!(err.kind(), ErrorKind::NoSyncSupport); + assert_eq!(err.to_string(), "op doesn't support sync calls"); + } + + #[test] + #[should_panic] + fn test_apply_source_map_invalid() { + let getter = MockSourceMapGetter {}; + let err = new(ErrorKind::NotFound, "not found".to_string()); + err.apply_source_map(&getter); + } + + #[test] + #[should_panic] + fn test_err_check() { + err_check( + Err(new(ErrorKind::NotFound, "foo".to_string())) as Result<(), DenoError> + ); + } +} diff --git a/cli/diagnostics.rs b/cli/diagnostics.rs index af384f277..69b58459e 100644 --- a/cli/diagnostics.rs +++ b/cli/diagnostics.rs @@ -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>, @@ -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 { @@ -229,10 +220,10 @@ 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), )); } @@ -240,74 +231,24 @@ impl DisplayFormatter for DiagnosticItem { } 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, ) } } @@ -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(()) + ) } } @@ -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\n ► deno/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\n► deno/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())); } } diff --git a/cli/dispatch_minimal.rs b/cli/dispatch_minimal.rs index e00203576..bda9ea535 100644 --- a/cli/dispatch_minimal.rs +++ b/cli/dispatch_minimal.rs @@ -124,27 +124,27 @@ pub fn dispatch_minimal( } mod ops { - use crate::errors; + use crate::deno_error; use crate::resources; use crate::tokio_write; use deno::PinnedBuf; use futures::Future; - type MinimalOp = dyn Future<Item = i32, Error = errors::DenoError> + Send; + type MinimalOp = dyn Future<Item = i32, Error = deno_error::DenoError> + Send; pub fn read(rid: i32, zero_copy: Option<PinnedBuf>) -> Box<MinimalOp> { debug!("read rid={}", rid); let zero_copy = match zero_copy { None => { - return Box::new(futures::future::err(errors::no_buffer_specified())) + return Box::new(futures::future::err(deno_error::no_buffer_specified())) } Some(buf) => buf, }; match resources::lookup(rid as u32) { - None => Box::new(futures::future::err(errors::bad_resource())), + None => Box::new(futures::future::err(deno_error::bad_resource())), Some(resource) => Box::new( tokio::io::read(resource, zero_copy) - .map_err(errors::DenoError::from) + .map_err(deno_error::DenoError::from) .and_then(move |(_resource, _buf, nread)| Ok(nread as i32)), ), } @@ -154,15 +154,15 @@ mod ops { debug!("write rid={}", rid); let zero_copy = match zero_copy { None => { - return Box::new(futures::future::err(errors::no_buffer_specified())) + return Box::new(futures::future::err(deno_error::no_buffer_specified())) } Some(buf) => buf, }; match resources::lookup(rid as u32) { - None => Box::new(futures::future::err(errors::bad_resource())), + None => Box::new(futures::future::err(deno_error::bad_resource())), Some(resource) => Box::new( tokio_write::write(resource, zero_copy) - .map_err(errors::DenoError::from) + .map_err(deno_error::DenoError::from) .and_then(move |(_resource, _buf, nwritten)| Ok(nwritten as i32)), ), } diff --git a/cli/errors.rs b/cli/errors.rs deleted file mode 100644 index 67eb54ea7..000000000 --- a/cli/errors.rs +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use crate::import_map::ImportMapError; -use crate::js_errors::JSErrorColor; -pub use crate::msg::ErrorKind; -use crate::resolve_addr::ResolveAddrError; -use deno::JSError; -use hyper; -#[cfg(unix)] -use nix::{errno::Errno, Error as UnixError}; -use std; -use std::fmt; -use std::io; -use url; - -pub type DenoResult<T> = std::result::Result<T, DenoError>; - -#[derive(Debug)] -pub struct DenoError { - repr: Repr, -} - -#[derive(Debug)] -enum Repr { - Simple(ErrorKind, String), - IoErr(io::Error), - UrlErr(url::ParseError), - HyperErr(hyper::Error), - ImportMapErr(ImportMapError), -} - -pub fn new(kind: ErrorKind, msg: String) -> DenoError { - DenoError { - repr: Repr::Simple(kind, msg), - } -} - -impl DenoError { - pub fn kind(&self) -> ErrorKind { - match self.repr { - Repr::Simple(kind, ref _msg) => kind, - // Repr::Simple(kind) => kind, - Repr::IoErr(ref err) => { - use std::io::ErrorKind::*; - match err.kind() { - NotFound => ErrorKind::NotFound, - PermissionDenied => ErrorKind::PermissionDenied, - ConnectionRefused => ErrorKind::ConnectionRefused, - ConnectionReset => ErrorKind::ConnectionReset, - ConnectionAborted => ErrorKind::ConnectionAborted, - NotConnected => ErrorKind::NotConnected, - AddrInUse => ErrorKind::AddrInUse, - AddrNotAvailable => ErrorKind::AddrNotAvailable, - BrokenPipe => ErrorKind::BrokenPipe, - AlreadyExists => ErrorKind::AlreadyExists, - WouldBlock => ErrorKind::WouldBlock, - InvalidInput => ErrorKind::InvalidInput, - InvalidData => ErrorKind::InvalidData, - TimedOut => ErrorKind::TimedOut, - Interrupted => ErrorKind::Interrupted, - WriteZero => ErrorKind::WriteZero, - Other => ErrorKind::Other, - UnexpectedEof => ErrorKind::UnexpectedEof, - _ => unreachable!(), - } - } - Repr::UrlErr(ref err) => { - use url::ParseError::*; - match err { - EmptyHost => ErrorKind::EmptyHost, - IdnaError => ErrorKind::IdnaError, - InvalidPort => ErrorKind::InvalidPort, - InvalidIpv4Address => ErrorKind::InvalidIpv4Address, - InvalidIpv6Address => ErrorKind::InvalidIpv6Address, - InvalidDomainCharacter => ErrorKind::InvalidDomainCharacter, - RelativeUrlWithoutBase => ErrorKind::RelativeUrlWithoutBase, - RelativeUrlWithCannotBeABaseBase => { - ErrorKind::RelativeUrlWithCannotBeABaseBase - } - SetHostOnCannotBeABaseUrl => ErrorKind::SetHostOnCannotBeABaseUrl, - Overflow => ErrorKind::Overflow, - } - } - Repr::HyperErr(ref err) => { - // For some reason hyper::errors::Kind is private. - if err.is_parse() { - ErrorKind::HttpParse - } else if err.is_user() { - ErrorKind::HttpUser - } else if err.is_canceled() { - ErrorKind::HttpCanceled - } else if err.is_closed() { - ErrorKind::HttpClosed - } else { - ErrorKind::HttpOther - } - } - Repr::ImportMapErr(ref _err) => ErrorKind::ImportMapError, - } - } -} - -impl fmt::Display for DenoError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.repr { - Repr::Simple(_kind, ref err_str) => f.pad(err_str), - Repr::IoErr(ref err) => err.fmt(f), - Repr::UrlErr(ref err) => err.fmt(f), - Repr::HyperErr(ref err) => err.fmt(f), - Repr::ImportMapErr(ref err) => f.pad(&err.msg), - } - } -} - -impl std::error::Error for DenoError { - fn description(&self) -> &str { - match self.repr { - Repr::Simple(_kind, ref msg) => msg.as_str(), - Repr::IoErr(ref err) => err.description(), - Repr::UrlErr(ref err) => err.description(), - Repr::HyperErr(ref err) => err.description(), - Repr::ImportMapErr(ref err) => &err.msg, - } - } - - fn cause(&self) -> Option<&dyn std::error::Error> { - match self.repr { - Repr::Simple(_kind, ref _msg) => None, - Repr::IoErr(ref err) => Some(err), - Repr::UrlErr(ref err) => Some(err), - Repr::HyperErr(ref err) => Some(err), - Repr::ImportMapErr(ref _err) => None, - } - } -} - -impl From<io::Error> for DenoError { - #[inline] - fn from(err: io::Error) -> Self { - Self { - repr: Repr::IoErr(err), - } - } -} - -impl From<url::ParseError> for DenoError { - #[inline] - fn from(err: url::ParseError) -> Self { - Self { - repr: Repr::UrlErr(err), - } - } -} - -impl From<hyper::Error> for DenoError { - #[inline] - fn from(err: hyper::Error) -> Self { - Self { - repr: Repr::HyperErr(err), - } - } -} - -impl From<ResolveAddrError> for DenoError { - fn from(e: ResolveAddrError) -> Self { - match e { - ResolveAddrError::Syntax => Self { - repr: Repr::Simple( - ErrorKind::InvalidInput, - "invalid address syntax".to_string(), - ), - }, - ResolveAddrError::Resolution(io_err) => Self { - repr: Repr::IoErr(io_err), - }, - } - } -} - -#[cfg(unix)] -impl From<UnixError> for DenoError { - fn from(e: UnixError) -> Self { - match e { - UnixError::Sys(Errno::EPERM) => Self { - repr: Repr::Simple( - ErrorKind::PermissionDenied, - Errno::EPERM.desc().to_owned(), - ), - }, - UnixError::Sys(Errno::EINVAL) => Self { - repr: Repr::Simple( - ErrorKind::InvalidInput, - Errno::EINVAL.desc().to_owned(), - ), - }, - UnixError::Sys(Errno::ENOENT) => Self { - repr: Repr::Simple( - ErrorKind::NotFound, - Errno::ENOENT.desc().to_owned(), - ), - }, - UnixError::Sys(err) => Self { - repr: Repr::Simple(ErrorKind::UnixError, err.desc().to_owned()), - }, - _ => Self { - repr: Repr::Simple(ErrorKind::Other, format!("{}", e)), - }, - } - } -} - -impl From<ImportMapError> for DenoError { - fn from(err: ImportMapError) -> Self { - Self { - repr: Repr::ImportMapErr(err), - } - } -} - -pub fn bad_resource() -> DenoError { - new(ErrorKind::BadResource, String::from("bad resource id")) -} - -pub fn permission_denied() -> DenoError { - new( - ErrorKind::PermissionDenied, - String::from("permission denied"), - ) -} - -pub fn op_not_implemented() -> DenoError { - new(ErrorKind::OpNotAvaiable, String::from("op not implemented")) -} - -pub fn worker_init_failed() -> DenoError { - // TODO(afinch7) pass worker error data through here - new( - ErrorKind::WorkerInitFailed, - String::from("worker init failed"), - ) -} - -pub fn no_buffer_specified() -> DenoError { - new(ErrorKind::InvalidInput, String::from("no buffer specified")) -} - -pub fn no_async_support() -> DenoError { - new( - ErrorKind::NoAsyncSupport, - String::from("op doesn't support async calls"), - ) -} - -pub fn no_sync_support() -> DenoError { - new( - ErrorKind::NoSyncSupport, - String::from("op doesn't support sync calls"), - ) -} - -#[derive(Debug)] -pub enum RustOrJsError { - Rust(DenoError), - Js(JSError), -} - -impl From<DenoError> for RustOrJsError { - fn from(e: DenoError) -> Self { - RustOrJsError::Rust(e) - } -} - -impl From<JSError> for RustOrJsError { - fn from(e: JSError) -> Self { - RustOrJsError::Js(e) - } -} - -impl fmt::Display for RustOrJsError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - RustOrJsError::Rust(e) => e.fmt(f), - RustOrJsError::Js(e) => JSErrorColor(e).fmt(f), - } - } -} - -// TODO(ry) This is ugly. They are essentially the same type. -impl From<deno::JSErrorOr<DenoError>> for RustOrJsError { - fn from(e: deno::JSErrorOr<DenoError>) -> Self { - match e { - deno::JSErrorOr::JSError(err) => RustOrJsError::Js(err), - deno::JSErrorOr::Other(err) => RustOrJsError::Rust(err), - } - } -} diff --git a/cli/fmt_errors.rs b/cli/fmt_errors.rs new file mode 100644 index 000000000..957e799ca --- /dev/null +++ b/cli/fmt_errors.rs @@ -0,0 +1,289 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +//! This mod provides DenoError to unify errors across Deno. +use crate::ansi; +use deno::JSError; +use deno::StackFrame; +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) -> String; +} + +fn format_source_name(script_name: String, line: i64, column: i64) -> String { + let script_name_c = ansi::cyan(script_name); + let line_c = ansi::yellow((1 + line).to_string()); + let column_c = ansi::yellow((1 + column).to_string()); + format!("{}:{}:{}", script_name_c, line_c, column_c,) +} + +/// Formats optional source, line and column into a single string. +pub fn format_maybe_source_name( + script_name: Option<String>, + line: Option<i64>, + column: Option<i64>, +) -> String { + if script_name.is_none() { + return "".to_string(); + } + + assert!(line.is_some()); + assert!(column.is_some()); + format_source_name(script_name.unwrap(), line.unwrap(), column.unwrap()) +} + +/// 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 + // an empty source line when displayed, so need just short circuit here + if source_line.is_empty() { + return "".to_string(); + } + + assert!(start_column.is_some()); + assert!(end_column.is_some()); + let line = (1 + 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 = 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 { + ansi::red(s).to_string() + } else { + 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 a message to preface with `error: ` with ansi codes for red. +pub fn format_error_message(msg: String) -> String { + let preamble = ansi::red("error:".to_string()); + format!("{} {}", preamble, msg) +} + +/// Wrapper around JSError which provides color to_string. +pub struct JSErrorColor<'a>(pub &'a JSError); + +impl<'a> DisplayFormatter for JSErrorColor<'a> { + fn format_category_and_code(&self) -> String { + "".to_string() + } + + fn format_message(&self, _level: usize) -> String { + format!( + "{}{}", + ansi::red_bold("error: ".to_string()), + 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 { + let e = self.0; + 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, + ) + ) + } +} + +impl<'a> fmt::Display for JSErrorColor<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}{}{}", + self.format_message(0), + self.format_source_name(), + self.format_source_line(0), + )?; + + for frame in &self.0.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_source_name(frame.script_name.clone(), 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) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ansi::strip_ansi_codes; + + fn error1() -> JSError { + JSError { + message: "Error: foo bar".to_string(), + source_line: None, + script_resource_name: None, + line_number: None, + start_position: None, + end_position: None, + error_level: None, + start_column: None, + end_column: None, + frames: vec![ + StackFrame { + line: 4, + column: 16, + script_name: "foo_bar.ts".to_string(), + function_name: "foo".to_string(), + is_eval: false, + is_constructor: false, + is_wasm: false, + }, + StackFrame { + line: 5, + column: 20, + script_name: "bar_baz.ts".to_string(), + function_name: "qat".to_string(), + is_eval: false, + is_constructor: false, + is_wasm: false, + }, + StackFrame { + line: 1, + column: 1, + script_name: "deno_main.js".to_string(), + function_name: "".to_string(), + is_eval: false, + is_constructor: false, + is_wasm: false, + }, + ], + } + } + + #[test] + fn js_error_to_string() { + let e = error1(); + assert_eq!("error: 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(&JSErrorColor(&e).to_string())); + } + + #[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"); + } +} @@ -15,7 +15,7 @@ use std::os::unix::fs::DirBuilderExt; #[cfg(any(unix))] use std::os::unix::fs::PermissionsExt; -use crate::errors::DenoResult; +use crate::deno_error::DenoResult; pub fn write_file<T: AsRef<[u8]>>( filename: &Path, @@ -115,7 +115,7 @@ pub fn normalize_path(path: &Path) -> String { #[cfg(unix)] pub fn chown(path: &str, uid: u32, gid: u32) -> DenoResult<()> { - use crate::errors::DenoError; + use crate::deno_error::DenoError; let nix_uid = Uid::from_raw(uid); let nix_gid = Gid::from_raw(gid); unix_chown(path, Option::Some(nix_uid), Option::Some(nix_gid)) @@ -126,6 +126,6 @@ pub fn chown(path: &str, uid: u32, gid: u32) -> DenoResult<()> { pub fn chown(_path: &str, _uid: u32, _gid: u32) -> DenoResult<()> { // Noop // TODO: implement chown for Windows - use crate::errors; - Err(errors::op_not_implemented()) + use crate::deno_error; + Err(deno_error::op_not_implemented()) } diff --git a/cli/http_util.rs b/cli/http_util.rs index 13d1ce45b..a3310b1bf 100644 --- a/cli/http_util.rs +++ b/cli/http_util.rs @@ -1,6 +1,6 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use crate::errors; -use crate::errors::DenoError; +use crate::deno_error; +use crate::deno_error::DenoError; #[cfg(test)] use futures::future::{loop_fn, Loop}; use futures::{future, Future, Stream}; @@ -58,7 +58,7 @@ fn resolve_uri_from_location(base_uri: &Uri, location: &str) -> Uri { } #[cfg(test)] -use crate::errors::DenoResult; +use crate::deno_error::DenoResult; #[cfg(test)] use crate::tokio_util; #[cfg(test)] @@ -108,8 +108,8 @@ pub fn fetch_string_once( } else if response.status().is_client_error() || response.status().is_server_error() { - return Box::new(future::err(errors::new( - errors::ErrorKind::Other, + return Box::new(future::err(deno_error::new( + deno_error::ErrorKind::Other, format!("Import '{}' failed: {}", &url, response.status()), ))); } @@ -165,8 +165,8 @@ pub fn fetch_string( return Ok(Loop::Continue((client, new_url))); } if !response.status().is_success() { - return Err(errors::new( - errors::ErrorKind::NotFound, + return Err(deno_error::new( + deno_error::ErrorKind::NotFound, "module not found".to_string(), )); } diff --git a/cli/main.rs b/cli/main.rs index ef132927e..d56b07479 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -17,16 +17,16 @@ extern crate rand; mod ansi; pub mod compiler; pub mod deno_dir; +pub mod deno_error; pub mod diagnostics; mod dispatch_minimal; -pub mod errors; pub mod flags; +pub mod fmt_errors; mod fs; mod global_timer; mod http_body; mod http_util; mod import_map; -pub mod js_errors; pub mod msg; pub mod msg_util; pub mod ops; @@ -36,6 +36,7 @@ mod repl; pub mod resolve_addr; pub mod resources; mod signal; +pub mod source_maps; mod startup_data; pub mod state; mod tokio_util; @@ -44,7 +45,7 @@ pub mod version; pub mod worker; use crate::compiler::bundle_async; -use crate::errors::RustOrJsError; +use crate::deno_error::DenoError; use crate::progress::Progress; use crate::state::ThreadSafeState; use crate::worker::Worker; @@ -82,14 +83,14 @@ impl log::Log for Logger { fn flush(&self) {} } -fn print_err_and_exit(err: RustOrJsError) { +fn print_err_and_exit(err: DenoError) { eprintln!("{}", err.to_string()); std::process::exit(1); } fn js_check<E>(r: Result<(), E>) where - E: Into<RustOrJsError>, + E: Into<DenoError>, { if let Err(err) = r { print_err_and_exit(err.into()); @@ -258,9 +259,7 @@ fn xeval_command(flags: DenoFlags, argv: Vec<String>) { .then(|result| { js_check(result); Ok(()) - }).map_err(|(err, _worker): (RustOrJsError, Worker)| { - print_err_and_exit(err) - }) + }).map_err(|(err, _worker): (DenoError, Worker)| print_err_and_exit(err)) }); tokio_util::run(main_future); } @@ -295,9 +294,7 @@ fn run_repl(flags: DenoFlags, argv: Vec<String>) { .then(|result| { js_check(result); Ok(()) - }).map_err(|(err, _worker): (RustOrJsError, Worker)| { - print_err_and_exit(err) - }) + }).map_err(|(err, _worker): (DenoError, Worker)| print_err_and_exit(err)) }); tokio_util::run(main_future); } diff --git a/cli/msg.fbs b/cli/msg.fbs index 7e8292740..d76e70d85 100644 --- a/cli/msg.fbs +++ b/cli/msg.fbs @@ -133,12 +133,16 @@ enum ErrorKind: byte { // custom errors InvalidUri, InvalidSeekMode, - OpNotAvaiable, + OpNotAvailable, WorkerInitFailed, UnixError, NoAsyncSupport, NoSyncSupport, ImportMapError, + + // other kinds + Diagnostic, + JSError, } table Cwd {} diff --git a/cli/msg_util.rs b/cli/msg_util.rs index 71bcc19d9..0ecb7f0d6 100644 --- a/cli/msg_util.rs +++ b/cli/msg_util.rs @@ -1,7 +1,7 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. // Helpers for serialization. -use crate::errors; -use crate::errors::DenoResult; +use crate::deno_error; +use crate::deno_error::DenoResult; use crate::msg; use flatbuffers; @@ -104,7 +104,7 @@ pub fn deserialize_request( let u = header_msg.url().unwrap(); let u = Uri::from_str(u) - .map_err(|e| errors::new(msg::ErrorKind::InvalidUri, e.to_string()))?; + .map_err(|e| deno_error::new(msg::ErrorKind::InvalidUri, e.to_string()))?; *r.uri_mut() = u; if let Some(method) = header_msg.method() { diff --git a/cli/ops.rs b/cli/ops.rs index a9279f070..d4add61e0 100644 --- a/cli/ops.rs +++ b/cli/ops.rs @@ -2,14 +2,15 @@ use atty; use crate::ansi; use crate::deno_dir::resolve_path; +use crate::deno_error; +use crate::deno_error::err_check; +use crate::deno_error::DenoError; +use crate::deno_error::DenoResult; +use crate::deno_error::ErrorKind; use crate::dispatch_minimal::dispatch_minimal; use crate::dispatch_minimal::parse_min_record; -use crate::errors; -use crate::errors::{DenoError, DenoResult, ErrorKind}; use crate::fs as deno_fs; use crate::http_util; -use crate::js_errors::apply_source_map; -use crate::js_errors::JSErrorColor; use crate::msg; use crate::msg_util; use crate::rand; @@ -25,7 +26,6 @@ use crate::tokio_util; use crate::tokio_write; use crate::version; use crate::worker::Worker; -use deno::js_check; use deno::Buf; use deno::CoreOp; use deno::JSError; @@ -401,11 +401,11 @@ fn op_format_error( let orig_error = String::from(inner.error().unwrap()); let js_error = JSError::from_v8_exception(&orig_error).unwrap(); - let js_error_mapped = apply_source_map(&js_error, &state.dir); - let js_error_string = JSErrorColor(&js_error_mapped).to_string(); + let error_mapped = DenoError::from(js_error).apply_source_map(&state.dir); + let error_string = error_mapped.to_string(); let mut builder = FlatBufferBuilder::new(); - let new_error = builder.create_string(&js_error_string); + let new_error = builder.create_string(&error_string); let inner = msg::FormatErrorRes::create( &mut builder, @@ -496,7 +496,7 @@ fn op_fetch_module_meta_data( data: Option<PinnedBuf>, ) -> CliOpResult { if !base.sync() { - return Err(errors::no_async_support()); + return Err(deno_error::no_async_support()); } assert!(data.is_none()); let inner = base.inner_as_fetch_module_meta_data().unwrap(); @@ -563,7 +563,7 @@ fn op_global_timer_stop( data: Option<PinnedBuf>, ) -> CliOpResult { if !base.sync() { - return Err(errors::no_async_support()); + return Err(deno_error::no_async_support()); } assert!(data.is_none()); let state = state; @@ -578,7 +578,7 @@ fn op_global_timer( data: Option<PinnedBuf>, ) -> CliOpResult { if base.sync() { - return Err(errors::no_sync_support()); + return Err(deno_error::no_sync_support()); } assert!(data.is_none()); let cmd_id = base.cmd_id(); @@ -1002,7 +1002,7 @@ fn op_close( let inner = base.inner_as_close().unwrap(); let rid = inner.rid(); match resources::lookup(rid) { - None => Err(errors::bad_resource()), + None => Err(deno_error::bad_resource()), Some(resource) => { resource.close(); ok_buf(empty_buf()) @@ -1033,7 +1033,7 @@ fn op_shutdown( let rid = inner.rid(); let how = inner.how(); match resources::lookup(rid) { - None => Err(errors::bad_resource()), + None => Err(deno_error::bad_resource()), Some(mut resource) => { let shutdown_mode = match how { 0 => Shutdown::Read, @@ -1059,7 +1059,7 @@ fn op_read( let rid = inner.rid(); match resources::lookup(rid) { - None => Err(errors::bad_resource()), + None => Err(deno_error::bad_resource()), Some(resource) => { let op = tokio::io::read(resource, data.unwrap()) .map_err(DenoError::from) @@ -1102,7 +1102,7 @@ fn op_write( let rid = inner.rid(); match resources::lookup(rid) { - None => Err(errors::bad_resource()), + None => Err(deno_error::bad_resource()), Some(resource) => { let op = tokio_write::write(resource, data.unwrap()) .map_err(DenoError::from) @@ -1146,7 +1146,7 @@ fn op_seek( let whence = inner.whence(); match resources::lookup(rid) { - None => Err(errors::bad_resource()), + None => Err(deno_error::bad_resource()), Some(resource) => { let op = resources::seek(resource, offset, whence) .and_then(move |_| Ok(empty_buf())); @@ -1205,7 +1205,7 @@ fn op_copy_file( // See https://github.com/rust-lang/rust/issues/54800 // Once the issue is reolved, we should remove this workaround. if cfg!(unix) && !from.is_file() { - return Err(errors::new( + return Err(deno_error::new( ErrorKind::NotFound, "File not found".to_string(), )); @@ -1417,7 +1417,10 @@ fn op_symlink( state.check_write(&newname_)?; // TODO Use type for Windows. if cfg!(windows) { - return Err(errors::new(ErrorKind::Other, "Not implemented".to_string())); + return Err(deno_error::new( + ErrorKind::Other, + "Not implemented".to_string(), + )); } blocking(base.sync(), move || { debug!("op_symlink {} {}", oldname.display(), newname.display()); @@ -1638,7 +1641,7 @@ fn op_accept( let server_rid = inner.rid(); match resources::lookup(server_rid) { - None => Err(errors::bad_resource()), + None => Err(deno_error::bad_resource()), Some(server_resource) => { let op = tokio_util::accept(server_resource) .map_err(DenoError::from) @@ -1767,7 +1770,7 @@ fn op_run( data: Option<PinnedBuf>, ) -> CliOpResult { if !base.sync() { - return Err(errors::no_async_support()); + return Err(deno_error::no_async_support()); } let cmd_id = base.cmd_id(); @@ -1906,7 +1909,7 @@ fn op_worker_get_message( data: Option<PinnedBuf>, ) -> CliOpResult { if base.sync() { - return Err(errors::no_sync_support()); + return Err(deno_error::no_sync_support()); } assert!(data.is_none()); let cmd_id = base.cmd_id(); @@ -1952,7 +1955,7 @@ fn op_worker_post_message( }; tx.send(d) .wait() - .map_err(|e| errors::new(ErrorKind::Other, e.to_string()))?; + .map_err(|e| deno_error::new(ErrorKind::Other, e.to_string()))?; let builder = &mut FlatBufferBuilder::new(); ok_buf(serialize_response( @@ -1988,34 +1991,32 @@ fn op_create_worker( let mut worker = Worker::new(name, startup_data::deno_isolate_init(), child_state); - js_check(worker.execute("denoMain()")); - js_check(worker.execute("workerMain()")); + err_check(worker.execute("denoMain()")); + err_check(worker.execute("workerMain()")); let module_specifier = ModuleSpecifier::resolve_root(specifier)?; - let op = worker - .execute_mod_async(&module_specifier, false) - .and_then(move |()| { - let mut workers_tl = parent_state.workers.lock().unwrap(); - workers_tl.insert(rid, worker.shared()); - let builder = &mut FlatBufferBuilder::new(); - let msg_inner = msg::CreateWorkerRes::create( - builder, - &msg::CreateWorkerResArgs { rid }, - ); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(msg_inner.as_union_value()), - inner_type: msg::Any::CreateWorkerRes, - ..Default::default() - }, - )) - }).map_err(|err| match err { - errors::RustOrJsError::Js(_) => errors::worker_init_failed(), - errors::RustOrJsError::Rust(err) => err, - }); + let op = + worker + .execute_mod_async(&module_specifier, false) + .and_then(move |()| { + let mut workers_tl = parent_state.workers.lock().unwrap(); + workers_tl.insert(rid, worker.shared()); + let builder = &mut FlatBufferBuilder::new(); + let msg_inner = msg::CreateWorkerRes::create( + builder, + &msg::CreateWorkerResArgs { rid }, + ); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(msg_inner.as_union_value()), + inner_type: msg::Any::CreateWorkerRes, + ..Default::default() + }, + )) + }); let result = op.wait()?; Ok(Op::Sync(result)) @@ -2028,7 +2029,7 @@ fn op_host_get_worker_closed( data: Option<PinnedBuf>, ) -> CliOpResult { if base.sync() { - return Err(errors::no_sync_support()); + return Err(deno_error::no_sync_support()); } assert!(data.is_none()); let cmd_id = base.cmd_id(); @@ -2063,7 +2064,7 @@ fn op_host_get_message( data: Option<PinnedBuf>, ) -> CliOpResult { if base.sync() { - return Err(errors::no_sync_support()); + return Err(deno_error::no_sync_support()); } assert!(data.is_none()); let cmd_id = base.cmd_id(); @@ -2107,7 +2108,7 @@ fn op_host_post_message( resources::post_message_to_worker(rid, d) .wait() - .map_err(|e| errors::new(ErrorKind::Other, e.to_string()))?; + .map_err(|e| deno_error::new(ErrorKind::Other, e.to_string()))?; let builder = &mut FlatBufferBuilder::new(); ok_buf(serialize_response( diff --git a/cli/permissions.rs b/cli/permissions.rs index e2e3e22a4..222016532 100644 --- a/cli/permissions.rs +++ b/cli/permissions.rs @@ -4,8 +4,8 @@ use atty; use crate::flags::DenoFlags; use ansi_term::Style; -use crate::errors::permission_denied; -use crate::errors::DenoResult; +use crate::deno_error::permission_denied; +use crate::deno_error::DenoResult; use std::collections::HashSet; use std::fmt; use std::io; diff --git a/cli/repl.rs b/cli/repl.rs index 86fa92e08..e0d029439 100644 --- a/cli/repl.rs +++ b/cli/repl.rs @@ -5,8 +5,8 @@ use crate::msg::ErrorKind; use std::error::Error; use crate::deno_dir::DenoDir; -use crate::errors::new as deno_error; -use crate::errors::DenoResult; +use crate::deno_error::new as deno_error; +use crate::deno_error::DenoResult; use std::path::PathBuf; #[cfg(not(windows))] diff --git a/cli/resources.rs b/cli/resources.rs index a6cee811f..e98327b63 100644 --- a/cli/resources.rs +++ b/cli/resources.rs @@ -8,10 +8,10 @@ // descriptors". This module implements a global resource table. Ops (AKA // handlers) look up resources by their integer id here. -use crate::errors; -use crate::errors::bad_resource; -use crate::errors::DenoError; -use crate::errors::DenoResult; +use crate::deno_error; +use crate::deno_error::bad_resource; +use crate::deno_error::DenoError; +use crate::deno_error::DenoResult; use crate::http_body::HttpBody; use crate::repl::Repl; use crate::state::WorkerChannels; @@ -372,10 +372,9 @@ impl Future for WorkerReceiver { let mut table = RESOURCE_TABLE.lock().unwrap(); let maybe_repr = table.get_mut(&self.rid); match maybe_repr { - Some(Repr::Worker(ref mut wc)) => wc - .1 - .poll() - .map_err(|err| errors::new(errors::ErrorKind::Other, err.to_string())), + Some(Repr::Worker(ref mut wc)) => wc.1.poll().map_err(|err| { + deno_error::new(deno_error::ErrorKind::Other, err.to_string()) + }), _ => Err(bad_resource()), } } @@ -398,10 +397,9 @@ impl Stream for WorkerReceiverStream { let mut table = RESOURCE_TABLE.lock().unwrap(); let maybe_repr = table.get_mut(&self.rid); match maybe_repr { - Some(Repr::Worker(ref mut wc)) => wc - .1 - .poll() - .map_err(|err| errors::new(errors::ErrorKind::Other, err.to_string())), + Some(Repr::Worker(ref mut wc)) => wc.1.poll().map_err(|err| { + deno_error::new(deno_error::ErrorKind::Other, err.to_string()) + }), _ => Err(bad_resource()), } } @@ -535,8 +533,8 @@ pub fn seek( 1 => SeekFrom::Current(i64::from(offset)), 2 => SeekFrom::End(i64::from(offset)), _ => { - return Box::new(futures::future::err(errors::new( - errors::ErrorKind::InvalidSeekMode, + return Box::new(futures::future::err(deno_error::new( + deno_error::ErrorKind::InvalidSeekMode, format!("Invalid seek mode: {}", whence), ))); } diff --git a/cli/signal.rs b/cli/signal.rs index 7d67ba743..bedb43ad2 100644 --- a/cli/signal.rs +++ b/cli/signal.rs @@ -3,11 +3,11 @@ use nix::sys::signal::{kill as unix_kill, Signal}; #[cfg(unix)] use nix::unistd::Pid; -use crate::errors::DenoResult; +use crate::deno_error::DenoResult; #[cfg(unix)] pub fn kill(pid: i32, signo: i32) -> DenoResult<()> { - use crate::errors::DenoError; + use crate::deno_error::DenoError; let sig = Signal::from_c_int(signo)?; unix_kill(Pid::from_raw(pid), Option::Some(sig)).map_err(DenoError::from) } diff --git a/cli/js_errors.rs b/cli/source_maps.rs index ae49ed636..11c3a3532 100644 --- a/cli/js_errors.rs +++ b/cli/source_maps.rs @@ -1,23 +1,18 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -//! This mod adds source maps and ANSI color display to deno::JSError. -use crate::ansi; +//! This mod provides functions to remap a deno::JSError based on a source map use deno::JSError; use deno::StackFrame; +use serde_json; use source_map_mappings::parse_mappings; use source_map_mappings::Bias; use source_map_mappings::Mappings; use std::collections::HashMap; -use std::fmt; use std::str; -/// Wrapper around JSError which provides color to_string. -pub struct JSErrorColor<'a>(pub &'a JSError); - -struct StackFrameColor<'a>(&'a StackFrame); - pub trait SourceMapGetter { /// Returns the raw source map file. fn get_source_map(&self, script_name: &str) -> Option<Vec<u8>>; + fn get_source_line(&self, script_name: &str, line: usize) -> Option<String>; } /// Cached filename lookups. The key can be None if a previous lookup failed to @@ -29,80 +24,9 @@ struct SourceMap { sources: Vec<String>, } -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) -} - -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(()) - } -} - impl SourceMap { + /// Take a JSON string and attempt to decode it, returning an optional + /// instance of `SourceMap`. fn from_json(json_str: &str) -> Option<Self> { // Ugly. Maybe use serde_derive. match serde_json::from_str::<serde_json::Value>(json_str) { @@ -137,110 +61,178 @@ impl SourceMap { } } -fn frame_apply_source_map<G: SourceMapGetter>( - frame: &StackFrame, - mappings_map: &mut CachedMaps, - getter: &G, -) -> StackFrame { - let maybe_sm = get_mappings(frame.script_name.as_ref(), mappings_map, getter); - let frame_pos = ( - frame.script_name.to_owned(), - frame.line as i64, - frame.column as i64, - ); - let (script_name, line, column) = match maybe_sm { - None => frame_pos, - Some(sm) => match sm.mappings.original_location_for( - frame.line as u32, - frame.column as u32, - Bias::default(), - ) { - None => frame_pos, - Some(mapping) => match &mapping.original { - None => frame_pos, - Some(original) => { - let orig_source = sm.sources[original.source as usize].clone(); - ( - orig_source, - i64::from(original.original_line), - i64::from(original.original_column), - ) - } - }, - }, - }; +// The bundle does not get built for 'cargo check', so we don't embed the +// bundle source map. The built in source map is the source map for the main +// JavaScript bundle which is then used to create the snapshot. Runtime stack +// traces can contain positions within the bundle which we will map to the +// original Deno TypeScript code. +#[cfg(feature = "check-only")] +fn builtin_source_map(_: &str) -> Option<Vec<u8>> { + None +} - StackFrame { - script_name, - function_name: frame.function_name.clone(), - line, - column, - is_eval: frame.is_eval, - is_constructor: frame.is_constructor, - is_wasm: frame.is_wasm, +#[cfg(not(feature = "check-only"))] +fn builtin_source_map(script_name: &str) -> Option<Vec<u8>> { + match script_name { + "gen/cli/bundle/main.js" => Some( + include_bytes!(concat!( + env!("GN_OUT_DIR"), + "/gen/cli/bundle/main.js.map" + )).to_vec(), + ), + "gen/cli/bundle/compiler.js" => Some( + include_bytes!(concat!( + env!("GN_OUT_DIR"), + "/gen/cli/bundle/compiler.js.map" + )).to_vec(), + ), + _ => None, } } +/// Apply a source map to a JSError, returning a JSError where the filenames, +/// the lines and the columns point to their original source location, not their +/// transpiled location if applicable. pub fn apply_source_map<G: SourceMapGetter>( js_error: &JSError, getter: &G, ) -> JSError { let mut mappings_map: CachedMaps = HashMap::new(); + let mut frames = Vec::<StackFrame>::new(); for frame in &js_error.frames { let f = frame_apply_source_map(&frame, &mut mappings_map, getter); frames.push(f); } + + let (script_resource_name, line_number, start_column) = + get_maybe_orig_position( + js_error.script_resource_name.clone(), + js_error.line_number, + js_error.start_column, + &mut mappings_map, + getter, + ); + // It is better to just move end_column to be the same distance away from + // start column because sometimes the code point is not available in the + // source file map. + let end_column = match js_error.end_column { + Some(ec) => { + if start_column.is_some() { + Some(ec - (js_error.start_column.unwrap() - start_column.unwrap())) + } else { + None + } + } + _ => None, + }; + // if there is a source line that we might be different in the source file, we + // will go fetch it from the getter + let source_line = if js_error.source_line.is_some() + && script_resource_name.is_some() + && line_number.is_some() + { + getter.get_source_line( + &js_error.script_resource_name.clone().unwrap(), + line_number.unwrap() as usize, + ) + } else { + js_error.source_line.clone() + }; + JSError { message: js_error.message.clone(), frames, error_level: js_error.error_level, - source_line: js_error.source_line.clone(), - // TODO the following need to be source mapped: - script_resource_name: js_error.script_resource_name.clone(), - line_number: js_error.line_number, + source_line, + script_resource_name, + line_number, + start_column, + end_column, + // These are difficult to map to their original position and they are not + // currently used in any output, so we don't remap them. start_position: js_error.start_position, end_position: js_error.end_position, - start_column: js_error.start_column, - end_column: js_error.end_column, } } -// The bundle does not get built for 'cargo check', so we don't embed the -// bundle source map. -#[cfg(feature = "check-only")] -fn builtin_source_map(_: &str) -> Option<Vec<u8>> { - None +fn frame_apply_source_map<G: SourceMapGetter>( + frame: &StackFrame, + mappings_map: &mut CachedMaps, + getter: &G, +) -> StackFrame { + let (script_name, line, column) = get_orig_position( + frame.script_name.to_string(), + frame.line, + frame.column, + mappings_map, + getter, + ); + + StackFrame { + script_name, + function_name: frame.function_name.clone(), + line, + column, + is_eval: frame.is_eval, + is_constructor: frame.is_constructor, + is_wasm: frame.is_wasm, + } } -#[cfg(not(feature = "check-only"))] -fn builtin_source_map(script_name: &str) -> Option<Vec<u8>> { - match script_name { - "gen/cli/bundle/main.js" => Some( - include_bytes!(concat!( - env!("GN_OUT_DIR"), - "/gen/cli/bundle/main.js.map" - )).to_vec(), - ), - "gen/cli/bundle/compiler.js" => Some( - include_bytes!(concat!( - env!("GN_OUT_DIR"), - "/gen/cli/bundle/compiler.js.map" - )).to_vec(), - ), - _ => None, +fn get_maybe_orig_position<G: SourceMapGetter>( + script_name: Option<String>, + line: Option<i64>, + column: Option<i64>, + mappings_map: &mut CachedMaps, + getter: &G, +) -> (Option<String>, Option<i64>, Option<i64>) { + match (script_name, line, column) { + (Some(script_name_v), Some(line_v), Some(column_v)) => { + let (script_name, line, column) = get_orig_position( + script_name_v, + line_v - 1, + column_v, + mappings_map, + getter, + ); + (Some(script_name), Some(line), Some(column)) + } + _ => (None, None, None), } } -fn parse_map_string<G: SourceMapGetter>( - script_name: &str, +fn get_orig_position<G: SourceMapGetter>( + script_name: String, + line: i64, + column: i64, + mappings_map: &mut CachedMaps, getter: &G, -) -> Option<SourceMap> { - builtin_source_map(script_name) - .or_else(|| getter.get_source_map(script_name)) - .and_then(|raw_source_map| { - SourceMap::from_json(str::from_utf8(&raw_source_map).unwrap()) - }) +) -> (String, i64, i64) { + let maybe_sm = get_mappings(&script_name, mappings_map, getter); + let default_pos = (script_name, line, column); + + match maybe_sm { + None => default_pos, + Some(sm) => match sm.mappings.original_location_for( + line as u32, + column as u32, + Bias::default(), + ) { + None => default_pos, + Some(mapping) => match &mapping.original { + None => default_pos, + Some(original) => { + let orig_source = sm.sources[original.source as usize].clone(); + ( + orig_source, + i64::from(original.original_line), + i64::from(original.original_column), + ) + } + }, + }, + } } fn get_mappings<'a, G: SourceMapGetter>( @@ -253,10 +245,57 @@ fn get_mappings<'a, G: SourceMapGetter>( .or_insert_with(|| parse_map_string(script_name, getter)) } +// TODO(kitsonk) parsed source maps should probably be cached in state in +// the module meta data. +fn parse_map_string<G: SourceMapGetter>( + script_name: &str, + getter: &G, +) -> Option<SourceMap> { + builtin_source_map(script_name) + .or_else(|| getter.get_source_map(script_name)) + .and_then(|raw_source_map| { + SourceMap::from_json(str::from_utf8(&raw_source_map).unwrap()) + }) +} + #[cfg(test)] mod tests { use super::*; - use crate::ansi::strip_ansi_codes; + + struct MockSourceMapGetter {} + + impl SourceMapGetter for MockSourceMapGetter { + fn get_source_map(&self, script_name: &str) -> Option<Vec<u8>> { + let s = match script_name { + "foo_bar.ts" => r#"{"sources": ["foo_bar.ts"], "mappings":";;;IAIA,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAE3C,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC"}"#, + "bar_baz.ts" => r#"{"sources": ["bar_baz.ts"], "mappings":";;;IAEA,CAAC,KAAK,IAAI,EAAE;QACV,MAAM,GAAG,GAAG,sDAAa,OAAO,2BAAC,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC,EAAE,CAAC;IAEQ,QAAA,GAAG,GAAG,KAAK,CAAC;IAEzB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC"}"#, + _ => return None, + }; + Some(s.as_bytes().to_owned()) + } + + fn get_source_line( + &self, + script_name: &str, + line: usize, + ) -> Option<String> { + let s = match script_name { + "foo_bar.ts" => vec![ + "console.log('foo');", + "console.log('foo');", + "console.log('foo');", + "console.log('foo');", + "console.log('foo');", + ], + _ => return None, + }; + if s.len() > line { + Some(s[line].to_string()) + } else { + None + } + } + } fn error1() -> JSError { JSError { @@ -301,25 +340,6 @@ mod tests { } } - struct MockSourceMapGetter {} - - impl SourceMapGetter for MockSourceMapGetter { - fn get_source_map(&self, script_name: &str) -> Option<Vec<u8>> { - let s = match script_name { - "foo_bar.ts" => r#"{"sources": ["foo_bar.ts"], "mappings":";;;IAIA,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAE3C,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC"}"#, - "bar_baz.ts" => r#"{"sources": ["bar_baz.ts"], "mappings":";;;IAEA,CAAC,KAAK,IAAI,EAAE;QACV,MAAM,GAAG,GAAG,sDAAa,OAAO,2BAAC,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC,EAAE,CAAC;IAEQ,QAAA,GAAG,GAAG,KAAK,CAAC;IAEzB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC"}"#, - _ => return None, - }; - Some(s.as_bytes().to_owned()) - } - } - - #[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", strip_ansi_codes(&e.to_string())); - } - #[test] fn js_error_apply_source_map_1() { let e = error1(); @@ -399,6 +419,25 @@ mod tests { } #[test] + fn js_error_apply_source_map_line() { + let e = JSError { + message: "TypeError: baz".to_string(), + source_line: Some("foo".to_string()), + script_resource_name: Some("foo_bar.ts".to_string()), + line_number: Some(4), + start_position: None, + end_position: None, + error_level: None, + start_column: Some(16), + end_column: None, + frames: vec![], + }; + let getter = MockSourceMapGetter {}; + let actual = apply_source_map(&e, &getter); + assert_eq!(actual.source_line, Some("console.log('foo');".to_string())); + } + + #[test] fn source_map_from_json() { let json = r#"{"version":3,"file":"error_001.js","sourceRoot":"","sources":["file:///Users/rld/src/deno/tests/error_001.ts"],"names":[],"mappings":"AAAA,SAAS,GAAG;IACV,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC;AACrB,CAAC;AAED,SAAS,GAAG;IACV,GAAG,EAAE,CAAC;AACR,CAAC;AAED,GAAG,EAAE,CAAC"}"#; let sm = SourceMap::from_json(json).unwrap(); diff --git a/cli/state.rs b/cli/state.rs index aa4690d44..69a2bf74d 100644 --- a/cli/state.rs +++ b/cli/state.rs @@ -2,8 +2,8 @@ use crate::compiler::compile_async; use crate::compiler::ModuleMetaData; use crate::deno_dir; -use crate::errors::DenoError; -use crate::errors::DenoResult; +use crate::deno_error::DenoError; +use crate::deno_error::DenoResult; use crate::flags; use crate::global_timer::GlobalTimer; use crate::import_map::ImportMap; diff --git a/cli/worker.rs b/cli/worker.rs index 9170a293f..65da666e8 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -1,11 +1,8 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use crate::errors::DenoError; -use crate::errors::RustOrJsError; -use crate::js_errors; +use crate::deno_error::DenoError; use crate::state::ThreadSafeState; use crate::tokio_util; use deno; -use deno::JSError; use deno::ModuleSpecifier; use deno::StartupData; use futures::Async; @@ -39,7 +36,7 @@ impl Worker { } /// Same as execute2() but the filename defaults to "<anonymous>". - pub fn execute(&mut self, js_source: &str) -> Result<(), JSError> { + pub fn execute(&mut self, js_source: &str) -> Result<(), DenoError> { self.execute2("<anonymous>", js_source) } @@ -49,9 +46,12 @@ impl Worker { &mut self, js_filename: &str, js_source: &str, - ) -> Result<(), JSError> { + ) -> Result<(), DenoError> { let mut isolate = self.isolate.lock().unwrap(); - isolate.execute(js_filename, js_source) + match isolate.execute(js_filename, js_source) { + Ok(_) => Ok(()), + Err(err) => Err(DenoError::from(err)), + } } /// Executes the provided JavaScript module. @@ -59,7 +59,7 @@ impl Worker { &mut self, module_specifier: &ModuleSpecifier, is_prefetch: bool, - ) -> impl Future<Item = (), Error = RustOrJsError> { + ) -> impl Future<Item = (), Error = DenoError> { let worker = self.clone(); let worker_ = worker.clone(); let loader = self.state.clone(); @@ -87,12 +87,12 @@ impl Worker { } }).map_err(move |err| { worker_.state.progress.done(); - // Convert to RustOrJsError AND apply_source_map. + // Convert to DenoError AND apply_source_map. match err { deno::JSErrorOr::JSError(err) => { - RustOrJsError::Js(worker_.apply_source_map(err)) + worker_.apply_source_map(DenoError::from(err)) } - deno::JSErrorOr::Other(err) => RustOrJsError::Rust(err), + deno::JSErrorOr::Other(err) => err, } }) } @@ -102,29 +102,32 @@ impl Worker { &mut self, module_specifier: &ModuleSpecifier, is_prefetch: bool, - ) -> Result<(), RustOrJsError> { + ) -> Result<(), DenoError> { tokio_util::block_on(self.execute_mod_async(module_specifier, is_prefetch)) } /// Applies source map to the error. - fn apply_source_map(&self, err: JSError) -> JSError { - js_errors::apply_source_map(&err, &self.state.dir) + fn apply_source_map(&self, err: DenoError) -> DenoError { + err.apply_source_map(&self.state.dir) } } impl Future for Worker { type Item = (); - type Error = JSError; + type Error = DenoError; fn poll(&mut self) -> Result<Async<()>, Self::Error> { let mut isolate = self.isolate.lock().unwrap(); - isolate.poll().map_err(|err| self.apply_source_map(err)) + isolate + .poll() + .map_err(|err| self.apply_source_map(DenoError::from(err))) } } #[cfg(test)] mod tests { use super::*; + use crate::deno_error::err_check; use crate::flags; use crate::ops::op_selector_std; use crate::progress::Progress; @@ -132,7 +135,6 @@ mod tests { use crate::startup_data; use crate::state::ThreadSafeState; use crate::tokio_util; - use deno::js_check; use futures::future::lazy; use std::sync::atomic::Ordering; @@ -208,7 +210,7 @@ mod tests { startup_data::deno_isolate_init(), state, ); - js_check(worker.execute("denoMain()")); + err_check(worker.execute("denoMain()")); let result = worker.execute_mod(&module_specifier, false); if let Err(err) = result { eprintln!("execute_mod err {:?}", err); @@ -229,8 +231,8 @@ mod tests { ]); let mut worker = Worker::new("TEST".to_string(), startup_data::deno_isolate_init(), state); - js_check(worker.execute("denoMain()")); - js_check(worker.execute("workerMain()")); + err_check(worker.execute("denoMain()")); + err_check(worker.execute("workerMain()")); worker } @@ -251,7 +253,7 @@ mod tests { console.log("after postMessage"); } "#; - js_check(worker.execute(source)); + err_check(worker.execute(source)); let resource = worker.state.resource.clone(); let resource_ = resource.clone(); @@ -259,7 +261,7 @@ mod tests { tokio::spawn(lazy(move || { worker.then(move |r| -> Result<(), ()> { resource_.close(); - js_check(r); + err_check(r); Ok(()) }) })); @@ -289,7 +291,7 @@ mod tests { fn removed_from_resource_table_on_close() { tokio_util::init(|| { let mut worker = create_test_worker(); - js_check( + err_check( worker.execute("onmessage = () => { delete window.onmessage; }"), ); @@ -300,7 +302,7 @@ mod tests { .then(move |r| -> Result<(), ()> { resource.close(); println!("workers.rs after resource close"); - js_check(r); + err_check(r); Ok(()) }).shared(); @@ -322,7 +324,7 @@ mod tests { #[test] fn execute_mod_resolve_error() { tokio_util::init(|| { - // "foo" is not a vailid module specifier so this should return an error. + // "foo" is not a valid module specifier so this should return an error. let mut worker = create_test_worker(); let module_specifier = ModuleSpecifier::resolve_root("does-not-exist").unwrap(); |
