diff options
author | haturau <135221985+haturatu@users.noreply.github.com> | 2024-11-20 01:20:47 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-20 01:20:47 +0900 |
commit | 85719a67e59c7aa45bead26e4942d7df8b1b42d4 (patch) | |
tree | face0aecaac53e93ce2f23b53c48859bcf1a36ec /runtime/fmt_errors.rs | |
parent | 67697bc2e4a62a9670699fd18ad0dd8efc5bd955 (diff) | |
parent | 186b52731c6bb326c4d32905c5e732d082e83465 (diff) |
Merge branch 'denoland:main' into main
Diffstat (limited to 'runtime/fmt_errors.rs')
-rw-r--r-- | runtime/fmt_errors.rs | 224 |
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) } |