mirror of
https://github.com/denoland/deno.git
synced 2025-08-04 02:48:24 +00:00
feat(cli): show error cause recursion information (#16384)
This commit is contained in:
parent
6cd9343e8b
commit
193b8828c5
7 changed files with 161 additions and 21 deletions
|
@ -9,6 +9,37 @@ use deno_core::error::JsError;
|
|||
use deno_core::error::JsStackFrame;
|
||||
use std::fmt::Write as _;
|
||||
|
||||
/// Compares all properties of JsError, except for JsError::cause.
|
||||
/// This function is used to detect that 2 JsError objects in a JsError::cause
|
||||
/// chain are identical, ie. there is a recursive cause.
|
||||
/// 02_console.js, which also detects recursive causes, can use JS object
|
||||
/// comparisons to compare errors. We don't have access to JS object identity in
|
||||
/// format_js_error().
|
||||
fn errors_are_equal_without_cause(a: &JsError, b: &JsError) -> bool {
|
||||
a.name == b.name
|
||||
&& a.message == b.message
|
||||
&& a.stack == b.stack
|
||||
// `a.cause == b.cause` omitted, because it is absent in recursive errors,
|
||||
// despite the error being identical to a previously seen one.
|
||||
&& a.exception_message == b.exception_message
|
||||
&& a.frames == b.frames
|
||||
&& a.source_line == b.source_line
|
||||
&& a.source_line_frame_index == b.source_line_frame_index
|
||||
&& a.aggregated == b.aggregated
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ErrorReference<'a> {
|
||||
from: &'a JsError,
|
||||
to: &'a JsError,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct IndexedErrorReference<'a> {
|
||||
reference: ErrorReference<'a>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
// Keep in sync with `/core/error.js`.
|
||||
pub fn format_location(frame: &JsStackFrame) -> String {
|
||||
let _internal = frame
|
||||
|
@ -150,25 +181,90 @@ fn format_maybe_source_line(
|
|||
format!("\n{}{}\n{}{}", indent, source_line, indent, color_underline)
|
||||
}
|
||||
|
||||
fn format_js_error_inner(js_error: &JsError, is_child: bool) -> String {
|
||||
let mut s = String::new();
|
||||
s.push_str(&js_error.exception_message);
|
||||
if let Some(aggregated) = &js_error.aggregated {
|
||||
for aggregated_error in aggregated {
|
||||
let error_string = format_js_error_inner(aggregated_error, true);
|
||||
for line in error_string.trim_start_matches("Uncaught ").lines() {
|
||||
write!(s, "\n {}", line).unwrap();
|
||||
}
|
||||
fn find_recursive_cause(js_error: &JsError) -> Option<ErrorReference> {
|
||||
let mut history = Vec::<&JsError>::new();
|
||||
|
||||
let mut current_error: &JsError = js_error;
|
||||
|
||||
while let Some(cause) = ¤t_error.cause {
|
||||
history.push(current_error);
|
||||
|
||||
if let Some(seen) = history
|
||||
.iter()
|
||||
.find(|&el| errors_are_equal_without_cause(el, cause.as_ref()))
|
||||
{
|
||||
return Some(ErrorReference {
|
||||
from: current_error,
|
||||
to: *seen,
|
||||
});
|
||||
} else {
|
||||
current_error = cause;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn format_aggregated_error(
|
||||
aggregated_errors: &Vec<JsError>,
|
||||
circular_reference_index: usize,
|
||||
) -> String {
|
||||
let mut s = String::new();
|
||||
let mut nested_circular_reference_index = circular_reference_index;
|
||||
|
||||
for js_error in aggregated_errors {
|
||||
let aggregated_circular = find_recursive_cause(js_error);
|
||||
if aggregated_circular.is_some() {
|
||||
nested_circular_reference_index += 1;
|
||||
}
|
||||
let error_string = format_js_error_inner(
|
||||
js_error,
|
||||
aggregated_circular.map(|reference| IndexedErrorReference {
|
||||
reference,
|
||||
index: nested_circular_reference_index,
|
||||
}),
|
||||
false,
|
||||
);
|
||||
|
||||
for line in error_string.trim_start_matches("Uncaught ").lines() {
|
||||
write!(s, "\n {}", line).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
fn format_js_error_inner(
|
||||
js_error: &JsError,
|
||||
circular: Option<IndexedErrorReference>,
|
||||
include_source_code: bool,
|
||||
) -> String {
|
||||
let mut s = String::new();
|
||||
|
||||
s.push_str(&js_error.exception_message);
|
||||
|
||||
if let Some(circular) = &circular {
|
||||
if errors_are_equal_without_cause(js_error, circular.reference.to) {
|
||||
write!(s, " {}", cyan(format!("<ref *{}>", circular.index))).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(aggregated) = &js_error.aggregated {
|
||||
let aggregated_message = format_aggregated_error(
|
||||
aggregated,
|
||||
circular.as_ref().map_or(0, |circular| circular.index),
|
||||
);
|
||||
s.push_str(&aggregated_message);
|
||||
}
|
||||
|
||||
let column_number = js_error
|
||||
.source_line_frame_index
|
||||
.and_then(|i| js_error.frames.get(i).unwrap().column_number);
|
||||
s.push_str(&format_maybe_source_line(
|
||||
if is_child {
|
||||
None
|
||||
} else {
|
||||
if include_source_code {
|
||||
js_error.source_line.as_deref()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
column_number,
|
||||
true,
|
||||
|
@ -178,7 +274,16 @@ fn format_js_error_inner(js_error: &JsError, is_child: bool) -> String {
|
|||
write!(s, "\n at {}", format_frame(frame)).unwrap();
|
||||
}
|
||||
if let Some(cause) = &js_error.cause {
|
||||
let error_string = format_js_error_inner(cause, true);
|
||||
let is_caused_by_circular = circular.as_ref().map_or(false, |circular| {
|
||||
errors_are_equal_without_cause(circular.reference.from, js_error)
|
||||
});
|
||||
|
||||
let error_string = if is_caused_by_circular {
|
||||
cyan(format!("[Circular *{}]", circular.unwrap().index)).to_string()
|
||||
} else {
|
||||
format_js_error_inner(cause, circular, false)
|
||||
};
|
||||
|
||||
write!(
|
||||
s,
|
||||
"\nCaused by: {}",
|
||||
|
@ -191,7 +296,13 @@ fn format_js_error_inner(js_error: &JsError, is_child: bool) -> String {
|
|||
|
||||
/// Format a [`JsError`] for terminal output.
|
||||
pub fn format_js_error(js_error: &JsError) -> String {
|
||||
format_js_error_inner(js_error, false)
|
||||
let circular =
|
||||
find_recursive_cause(js_error).map(|reference| IndexedErrorReference {
|
||||
reference,
|
||||
index: 1,
|
||||
});
|
||||
|
||||
format_js_error_inner(js_error, circular, true)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue