summaryrefslogtreecommitdiff
path: root/runtime/fmt_errors.rs
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/fmt_errors.rs')
-rw-r--r--runtime/fmt_errors.rs224
1 files changed, 195 insertions, 29 deletions
diff --git a/runtime/fmt_errors.rs b/runtime/fmt_errors.rs
index 44a947732..6c05fbc63 100644
--- a/runtime/fmt_errors.rs
+++ b/runtime/fmt_errors.rs
@@ -1,11 +1,10 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
//! This mod provides DenoError to unify errors across Deno.
+use color_print::cformat;
+use color_print::cstr;
use deno_core::error::format_frame;
use deno_core::error::JsError;
-use deno_terminal::colors::cyan;
-use deno_terminal::colors::italic_bold;
-use deno_terminal::colors::red;
-use deno_terminal::colors::yellow;
+use deno_terminal::colors;
use std::fmt::Write as _;
#[derive(Debug, Clone)]
@@ -24,6 +23,7 @@ struct IndexedErrorReference<'a> {
enum FixSuggestionKind {
Info,
Hint,
+ Docs,
}
#[derive(Debug)]
@@ -66,6 +66,13 @@ impl<'a> FixSuggestion<'a> {
message: FixSuggestionMessage::Multiline(messages),
}
}
+
+ pub fn docs(url: &'a str) -> Self {
+ Self {
+ kind: FixSuggestionKind::Docs,
+ message: FixSuggestionMessage::Single(url),
+ }
+ }
}
struct AnsiColors;
@@ -78,10 +85,10 @@ impl deno_core::error::ErrorFormat for AnsiColors {
use deno_core::error::ErrorElement::*;
match element {
Anonymous | NativeFrame | FileName | EvalOrigin => {
- cyan(s).to_string().into()
+ colors::cyan(s).to_string().into()
}
- LineNumber | ColumnNumber => yellow(s).to_string().into(),
- FunctionName | PromiseAll => italic_bold(s).to_string().into(),
+ LineNumber | ColumnNumber => colors::yellow(s).to_string().into(),
+ FunctionName | PromiseAll => colors::italic_bold(s).to_string().into(),
}
}
}
@@ -114,7 +121,7 @@ fn format_maybe_source_line(
if column_number as usize > source_line.len() {
return format!(
"\n{} Couldn't format source line: Column {} is out of bounds (source may have changed at runtime)",
- yellow("Warning"), column_number,
+ colors::yellow("Warning"), column_number,
);
}
@@ -127,9 +134,9 @@ fn format_maybe_source_line(
}
s.push('^');
let color_underline = if is_error {
- red(&s).to_string()
+ colors::red(&s).to_string()
} else {
- cyan(&s).to_string()
+ colors::cyan(&s).to_string()
};
let indent = format!("{:indent$}", "", indent = level);
@@ -200,7 +207,8 @@ fn format_js_error_inner(
if let Some(circular) = &circular {
if js_error.is_same_error(circular.reference.to) {
- write!(s, " {}", cyan(format!("<ref *{}>", circular.index))).unwrap();
+ write!(s, " {}", colors::cyan(format!("<ref *{}>", circular.index)))
+ .unwrap();
}
}
@@ -238,7 +246,8 @@ fn format_js_error_inner(
.unwrap_or(false);
let error_string = if is_caused_by_circular {
- cyan(format!("[Circular *{}]", circular.unwrap().index)).to_string()
+ colors::cyan(format!("[Circular *{}]", circular.unwrap().index))
+ .to_string()
} else {
format_js_error_inner(cause, circular, false, vec![])
};
@@ -255,12 +264,23 @@ fn format_js_error_inner(
for (index, suggestion) in suggestions.iter().enumerate() {
write!(s, " ").unwrap();
match suggestion.kind {
- FixSuggestionKind::Hint => write!(s, "{} ", cyan("hint:")).unwrap(),
- FixSuggestionKind::Info => write!(s, "{} ", yellow("info:")).unwrap(),
+ FixSuggestionKind::Hint => {
+ write!(s, "{} ", colors::cyan("hint:")).unwrap()
+ }
+ FixSuggestionKind::Info => {
+ write!(s, "{} ", colors::yellow("info:")).unwrap()
+ }
+ FixSuggestionKind::Docs => {
+ write!(s, "{} ", colors::green("docs:")).unwrap()
+ }
};
match suggestion.message {
FixSuggestionMessage::Single(msg) => {
- write!(s, "{}", msg).unwrap();
+ if matches!(suggestion.kind, FixSuggestionKind::Docs) {
+ write!(s, "{}", cformat!("<u>{}</>", msg)).unwrap();
+ } else {
+ write!(s, "{}", msg).unwrap();
+ }
}
FixSuggestionMessage::Multiline(messages) => {
for (idx, message) in messages.iter().enumerate() {
@@ -282,28 +302,174 @@ fn format_js_error_inner(
s
}
-/// Format a [`JsError`] for terminal output.
-pub fn format_js_error(js_error: &JsError) -> String {
- let circular =
- find_recursive_cause(js_error).map(|reference| IndexedErrorReference {
- reference,
- index: 1,
- });
+fn get_suggestions_for_terminal_errors(e: &JsError) -> Vec<FixSuggestion> {
+ if let Some(msg) = &e.message {
+ if msg.contains("module is not defined")
+ || msg.contains("exports is not defined")
+ || msg.contains("require is not defined")
+ {
+ return vec![
+ FixSuggestion::info_multiline(&[
+ cstr!("Deno supports CommonJS modules in <u>.cjs</> files, or when the closest"),
+ cstr!("<u>package.json</> has a <i>\"type\": \"commonjs\"</> option.")
+ ]),
+ FixSuggestion::hint_multiline(&[
+ "Rewrite this module to ESM,",
+ cstr!("or change the file extension to <u>.cjs</u>,"),
+ cstr!("or add <u>package.json</> next to the file with <i>\"type\": \"commonjs\"</> option."),
+ ]),
+ FixSuggestion::docs("https://docs.deno.com/go/commonjs"),
+ ];
+ } else if msg.contains("__filename is not defined") {
+ return vec![
+ FixSuggestion::info(cstr!(
+ "<u>__filename</> global is not available in ES modules."
+ )),
+ FixSuggestion::hint(cstr!("Use <u>import.meta.filename</> instead.")),
+ ];
+ } else if msg.contains("__dirname is not defined") {
+ return vec![
+ FixSuggestion::info(cstr!(
+ "<u>__dirname</> global is not available in ES modules."
+ )),
+ FixSuggestion::hint(cstr!("Use <u>import.meta.dirname</> instead.")),
+ ];
+ } else if msg.contains("Buffer is not defined") {
+ return vec![
+ FixSuggestion::info(cstr!(
+ "<u>Buffer</> is not available in the global scope in Deno."
+ )),
+ FixSuggestion::hint_multiline(&[
+ cstr!("Import it explicitly with <u>import { Buffer } from \"node:buffer\";</>,"),
+ cstr!("or run again with <u>--unstable-node-globals</> flag to add this global."),
+ ]),
+ ];
+ } else if msg.contains("clearImmediate is not defined") {
+ return vec![
+ FixSuggestion::info(cstr!(
+ "<u>clearImmediate</> is not available in the global scope in Deno."
+ )),
+ FixSuggestion::hint_multiline(&[
+ cstr!("Import it explicitly with <u>import { clearImmediate } from \"node:timers\";</>,"),
+ cstr!("or run again with <u>--unstable-node-globals</> flag to add this global."),
+ ]),
+ ];
+ } else if msg.contains("setImmediate is not defined") {
+ return vec![
+ FixSuggestion::info(cstr!(
+ "<u>setImmediate</> is not available in the global scope in Deno."
+ )),
+ FixSuggestion::hint_multiline(
+ &[cstr!("Import it explicitly with <u>import { setImmediate } from \"node:timers\";</>,"),
+ cstr!("or run again with <u>--unstable-node-globals</> flag to add this global."),
+ ]),
+ ];
+ } else if msg.contains("global is not defined") {
+ return vec![
+ FixSuggestion::info(cstr!(
+ "<u>global</> is not available in the global scope in Deno."
+ )),
+ FixSuggestion::hint_multiline(&[
+ cstr!("Use <u>globalThis</> instead, or assign <u>globalThis.global = globalThis</>,"),
+ cstr!("or run again with <u>--unstable-node-globals</> flag to add this global."),
+ ]),
+ ];
+ } else if msg.contains("openKv is not a function") {
+ return vec![
+ FixSuggestion::info("Deno.openKv() is an unstable API."),
+ FixSuggestion::hint(
+ "Run again with `--unstable-kv` flag to enable this API.",
+ ),
+ ];
+ } else if msg.contains("cron is not a function") {
+ return vec![
+ FixSuggestion::info("Deno.cron() is an unstable API."),
+ FixSuggestion::hint(
+ "Run again with `--unstable-cron` flag to enable this API.",
+ ),
+ ];
+ } else if msg.contains("WebSocketStream is not defined") {
+ return vec![
+ FixSuggestion::info("new WebSocketStream() is an unstable API."),
+ FixSuggestion::hint(
+ "Run again with `--unstable-net` flag to enable this API.",
+ ),
+ ];
+ } else if msg.contains("Temporal is not defined") {
+ return vec![
+ FixSuggestion::info("Temporal is an unstable API."),
+ FixSuggestion::hint(
+ "Run again with `--unstable-temporal` flag to enable this API.",
+ ),
+ ];
+ } else if msg.contains("BroadcastChannel is not defined") {
+ return vec![
+ FixSuggestion::info("BroadcastChannel is an unstable API."),
+ FixSuggestion::hint(
+ "Run again with `--unstable-broadcast-channel` flag to enable this API.",
+ ),
+ ];
+ } else if msg.contains("window is not defined") {
+ return vec![
+ FixSuggestion::info("window global is not available in Deno 2."),
+ FixSuggestion::hint("Replace `window` with `globalThis`."),
+ ];
+ } else if msg.contains("UnsafeWindowSurface is not a constructor") {
+ return vec![
+ FixSuggestion::info("Deno.UnsafeWindowSurface is an unstable API."),
+ FixSuggestion::hint(
+ "Run again with `--unstable-webgpu` flag to enable this API.",
+ ),
+ ];
+ // Try to capture errors like:
+ // ```
+ // Uncaught Error: Cannot find module '../build/Release/canvas.node'
+ // Require stack:
+ // - /.../deno/npm/registry.npmjs.org/canvas/2.11.2/lib/bindings.js
+ // - /.../.cache/deno/npm/registry.npmjs.org/canvas/2.11.2/lib/canvas.js
+ // ```
+ } else if msg.contains("Cannot find module")
+ && msg.contains("Require stack")
+ && msg.contains(".node'")
+ {
+ return vec![
+ FixSuggestion::info_multiline(
+ &[
+ "Trying to execute an npm package using Node-API addons,",
+ "these packages require local `node_modules` directory to be present."
+ ]
+ ),
+ FixSuggestion::hint_multiline(
+ &[
+ "Add `\"nodeModulesDir\": \"auto\" option to `deno.json`, and then run",
+ "`deno install --allow-scripts=npm:<package> --entrypoint <script>` to setup `node_modules` directory."
+ ]
+ )
+ ];
+ } else if msg.contains("document is not defined") {
+ return vec![
+ FixSuggestion::info(cstr!(
+ "<u>document</> global is not available in Deno."
+ )),
+ FixSuggestion::hint_multiline(&[
+ cstr!("Use a library like <u>happy-dom</>, <u>deno_dom</>, <u>linkedom</> or <u>JSDom</>"),
+ cstr!("and setup the <u>document</> global according to the library documentation."),
+ ]),
+ ];
+ }
+ }
- format_js_error_inner(js_error, circular, true, vec![])
+ vec![]
}
-/// Format a [`JsError`] for terminal output, printing additional suggestions.
-pub fn format_js_error_with_suggestions(
- js_error: &JsError,
- suggestions: Vec<FixSuggestion>,
-) -> String {
+/// Format a [`JsError`] for terminal output.
+pub fn format_js_error(js_error: &JsError) -> String {
let circular =
find_recursive_cause(js_error).map(|reference| IndexedErrorReference {
reference,
index: 1,
});
-
+ let suggestions = get_suggestions_for_terminal_errors(js_error);
format_js_error_inner(js_error, circular, true, suggestions)
}