mod colors {
pub const RESET: &str = "\x1b[0m";
pub const RED: &str = "\x1b[31m";
pub const CYAN: &str = "\x1b[36m";
pub const GRAY: &str = "\x1b[90m";
pub const BOLD: &str = "\x1b[1m";
}
fn should_use_colors() -> bool {
if std::env::var("NO_COLOR").is_ok() {
return false;
}
if std::env::var("FORCE_COLOR").is_ok() {
return true;
}
true
}
#[inline]
pub(crate) fn throw_type_error(scope: &mut v8::PinScope, message: &str) {
let message = v8::String::new(scope, message).unwrap();
let exception = v8::Exception::type_error(scope, message);
scope.throw_exception(exception);
}
#[inline]
pub(crate) fn throw_error(scope: &mut v8::PinScope, message: &str) {
let message = v8::String::new(scope, message).unwrap();
let exception = v8::Exception::error(scope, message);
scope.throw_exception(exception);
}
#[allow(dead_code)] #[inline]
pub(crate) fn throw_range_error(scope: &mut v8::PinScope, message: &str) {
let message = v8::String::new(scope, message).unwrap();
let exception = v8::Exception::range_error(scope, message);
scope.throw_exception(exception);
}
pub(crate) fn try_to_rust_string(
scope: &mut v8::PinScope,
value: v8::Local<v8::Value>,
param_name: &str,
) -> Result<String, String> {
v8::tc_scope!(let tc, scope);
match value.to_string(tc) {
Some(s) => Ok(s.to_rust_string_lossy(tc)),
None => Err(format!("Failed to convert {} to string", param_name)),
}
}
pub(crate) fn to_rust_string_or_throw(
scope: &mut v8::PinScope,
value: v8::Local<v8::Value>,
param_name: &str,
) -> Option<String> {
match try_to_rust_string(scope, value, param_name) {
Ok(s) => Some(s),
Err(msg) => {
throw_type_error(scope, &msg);
None
}
}
}
pub(crate) fn check_arg_count(
scope: &mut v8::PinScope,
args: &v8::FunctionCallbackArguments,
min_count: usize,
function_name: &str,
) -> bool {
if args.length() < min_count as i32 {
let msg = format!(
"{} requires at least {} argument{}",
function_name,
min_count,
if min_count == 1 { "" } else { "s" }
);
throw_type_error(scope, &msg);
false
} else {
true
}
}
pub(crate) fn try_get_function_result<'s>(
value: v8::Local<'s, v8::Value>,
) -> Result<v8::Local<'s, v8::Function>, &'static str> {
v8::Local::<v8::Function>::try_from(value).map_err(|_| "Value must be a function")
}
#[allow(dead_code)] pub(crate) fn try_get_object_result<'s>(
value: v8::Local<'s, v8::Value>,
) -> Result<v8::Local<'s, v8::Object>, &'static str> {
v8::Local::<v8::Object>::try_from(value).map_err(|_| "Value must be an object")
}
pub(crate) fn try_get_array_result<'s>(
value: v8::Local<'s, v8::Value>,
) -> Result<v8::Local<'s, v8::Array>, &'static str> {
v8::Local::<v8::Array>::try_from(value).map_err(|_| "Value must be an array")
}
pub(crate) fn format_exception_value(
scope: &mut v8::PinScope,
exception: v8::Local<v8::Value>,
) -> String {
let use_colors = should_use_colors();
let exception_str = {
let isolate: &v8::Isolate = scope;
exception
.to_string(scope)
.map(|s| s.to_rust_string_lossy(isolate))
.unwrap_or_else(|| "Unknown error".to_string())
};
if let Ok(exception_obj) = v8::Local::<v8::Object>::try_from(exception) {
let isolate_mut: &mut v8::Isolate = scope;
let state = crate::IsolateState::get(isolate_mut);
let cache = state.borrow().string_cache.clone();
let mut cache_borrow = cache.borrow_mut();
let stack_key = crate::get_or_create_cached_string!(scope, cache_borrow, stack, "stack");
drop(cache_borrow);
if let Some(stack_val) = exception_obj.get(scope, stack_key.into())
&& let Some(stack_str) = stack_val.to_string(scope)
{
let isolate: &v8::Isolate = scope;
let stack = stack_str.to_rust_string_lossy(isolate);
if !stack.is_empty() && stack != exception_str {
let mut output = String::new();
if use_colors {
output.push_str(colors::GRAY);
}
output.push_str(&stack);
if use_colors {
output.push_str(colors::RESET);
}
return output;
}
}
}
if let Some(stripped) = exception_str.strip_prefix("Error: ") {
stripped.to_string()
} else {
exception_str
}
}
pub(crate) fn format_exception(
tc: &mut v8::PinnedRef<'_, v8::TryCatch<v8::HandleScope>>,
) -> String {
let use_colors = should_use_colors();
let exception = match tc.exception() {
Some(e) => e,
None => return "Unknown error".to_string(),
};
let exception_string = {
let isolate: &v8::Isolate = tc;
exception
.to_string(tc)
.map(|s| s.to_rust_string_lossy(isolate))
.unwrap_or_else(|| "Unknown error".to_string())
};
if let Some(message) = tc.message() {
let mut output = String::new();
let (resource_name, line_number) = {
let isolate: &v8::Isolate = tc;
(
message
.get_script_resource_name(tc)
.and_then(|v| v.to_string(tc))
.map(|s| s.to_rust_string_lossy(isolate)),
message.get_line_number(tc),
)
};
let source_line = {
let isolate: &v8::Isolate = tc;
message
.get_source_line(tc)
.map(|s| s.to_string(tc).unwrap().to_rust_string_lossy(isolate))
};
let start_column = message.get_start_column();
let end_column = message.get_end_column();
if let (Some(file), Some(line)) = (resource_name.as_ref(), line_number) {
if use_colors {
output.push_str(&format!(
"{}{}:{}{} \n",
colors::CYAN,
file,
line,
colors::RESET
));
} else {
output.push_str(&format!("{}:{}\n", file, line));
}
if let Some(ref source) = source_line {
output.push_str(source);
output.push('\n');
for _ in 0..start_column {
output.push(' ');
}
let caret_count = (end_column - start_column).max(1);
if use_colors {
output.push_str(colors::RED);
}
for _ in 0..caret_count {
output.push('^');
}
if use_colors {
output.push_str(colors::RESET);
}
output.push('\n');
}
}
output.push('\n');
if use_colors {
output.push_str(colors::RED);
output.push_str(colors::BOLD);
}
output.push_str(&exception_string);
if use_colors {
output.push_str(colors::RESET);
}
if let Ok(exception_obj) = v8::Local::<v8::Object>::try_from(exception) {
let isolate_mut: &mut v8::Isolate = tc;
let state = crate::IsolateState::get(isolate_mut);
let cache = state.borrow().string_cache.clone();
let mut cache_borrow = cache.borrow_mut();
let stack_key = crate::get_or_create_cached_string!(tc, cache_borrow, stack, "stack");
drop(cache_borrow);
if let Some(stack_val) = exception_obj.get(tc, stack_key.into())
&& let Some(stack_str) = stack_val.to_string(tc)
{
let isolate: &v8::Isolate = tc;
let stack = stack_str.to_rust_string_lossy(isolate);
if !stack.is_empty() && stack != exception_string && stack.contains('\n') {
if stack.starts_with(&exception_string) || stack.contains(&exception_string) {
output = String::new();
let (file_opt, line_opt) = {
let isolate: &v8::Isolate = tc;
(
message
.get_script_resource_name(tc)
.and_then(|v| v.to_string(tc))
.map(|s| s.to_rust_string_lossy(isolate)),
message.get_line_number(tc),
)
};
if let (Some(file), Some(line)) = (file_opt, line_opt) {
if use_colors {
output.push_str(&format!(
"{}{}:{}{} \n",
colors::CYAN,
file,
line,
colors::RESET
));
} else {
output.push_str(&format!("{}:{}\n", file, line));
}
let source_opt = {
let isolate: &v8::Isolate = tc;
message
.get_source_line(tc)
.map(|s| s.to_string(tc).unwrap().to_rust_string_lossy(isolate))
};
if let Some(source) = source_opt {
output.push_str(&source);
output.push('\n');
for _ in 0..start_column {
output.push(' ');
}
let caret_count = (end_column - start_column).max(1);
if use_colors {
output.push_str(colors::RED);
}
for _ in 0..caret_count {
output.push('^');
}
if use_colors {
output.push_str(colors::RESET);
}
output.push('\n');
}
output.push('\n');
}
if use_colors {
output.push_str(colors::GRAY);
}
output.push_str(&stack);
if use_colors {
output.push_str(colors::RESET);
}
} else {
output.push('\n');
if use_colors {
output.push_str(colors::GRAY);
}
output.push_str(&stack);
if use_colors {
output.push_str(colors::RESET);
}
}
}
}
}
output
} else {
exception_string
}
}