diff options
-rw-r--r-- | cli/main.rs | 23 | ||||
-rw-r--r-- | runtime/fmt_errors.rs | 63 | ||||
-rw-r--r-- | tests/specs/run/import_common_js/__test__.jsonc | 12 | ||||
-rw-r--r-- | tests/specs/run/import_common_js/exports_error.js | 1 | ||||
-rw-r--r-- | tests/specs/run/import_common_js/exports_error.out | 7 | ||||
-rw-r--r-- | tests/specs/run/import_common_js/module_error.js | 3 | ||||
-rw-r--r-- | tests/specs/run/import_common_js/module_error.out | 7 |
7 files changed, 111 insertions, 5 deletions
diff --git a/cli/main.rs b/cli/main.rs index 3b366306a..da274d8ed 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -46,7 +46,8 @@ use deno_core::error::JsError; use deno_core::futures::FutureExt; use deno_core::unsync::JoinHandle; use deno_npm::resolution::SnapshotFromLockfileError; -use deno_runtime::fmt_errors::format_js_error; +use deno_runtime::fmt_errors::format_js_error_with_suggestions; +use deno_runtime::fmt_errors::FixSuggestion; use deno_runtime::tokio_util::create_and_run_current_thread_with_maybe_metrics; use deno_terminal::colors; use factory::CliFactory; @@ -336,12 +337,30 @@ fn exit_with_message(message: &str, code: i32) -> ! { std::process::exit(code); } +fn get_suggestions_for_commonjs_error(e: &JsError) -> Vec<FixSuggestion> { + if e.name.as_deref() == Some("ReferenceError") { + if let Some(msg) = &e.message { + if msg.contains("module is not defined") + || msg.contains("exports is not defined") + { + return vec![ + FixSuggestion::info("Deno does not support CommonJS modules without `.cjs` extension."), + FixSuggestion::hint("Rewrite this module to ESM or change the file extension to `.cjs`."), + ]; + } + } + } + + vec![] +} + fn exit_for_error(error: AnyError) -> ! { let mut error_string = format!("{error:?}"); let mut error_code = 1; if let Some(e) = error.downcast_ref::<JsError>() { - error_string = format_js_error(e); + let suggestions = get_suggestions_for_commonjs_error(e); + error_string = format_js_error_with_suggestions(e, suggestions); } else if let Some(SnapshotFromLockfileError::IntegrityCheckFailed(e)) = error.downcast_ref::<SnapshotFromLockfileError>() { diff --git a/runtime/fmt_errors.rs b/runtime/fmt_errors.rs index 4687dbd47..b2cec2a5a 100644 --- a/runtime/fmt_errors.rs +++ b/runtime/fmt_errors.rs @@ -20,6 +20,34 @@ struct IndexedErrorReference<'a> { index: usize, } +#[derive(Debug)] +enum FixSuggestionKind { + Info, + Hint, +} + +#[derive(Debug)] +pub struct FixSuggestion<'a> { + kind: FixSuggestionKind, + message: &'a str, +} + +impl<'a> FixSuggestion<'a> { + pub fn info(message: &'a str) -> Self { + Self { + kind: FixSuggestionKind::Info, + message, + } + } + + pub fn hint(message: &'a str) -> Self { + Self { + kind: FixSuggestionKind::Hint, + message, + } + } +} + struct AnsiColors; impl deno_core::error::ErrorFormat for AnsiColors { @@ -129,6 +157,7 @@ fn format_aggregated_error( index: nested_circular_reference_index, }), false, + vec![], ); for line in error_string.trim_start_matches("Uncaught ").lines() { @@ -143,6 +172,7 @@ fn format_js_error_inner( js_error: &JsError, circular: Option<IndexedErrorReference>, include_source_code: bool, + suggestions: Vec<FixSuggestion>, ) -> String { let mut s = String::new(); @@ -190,7 +220,7 @@ fn format_js_error_inner( let error_string = if is_caused_by_circular { cyan(format!("[Circular *{}]", circular.unwrap().index)).to_string() } else { - format_js_error_inner(cause, circular, false) + format_js_error_inner(cause, circular, false, vec![]) }; write!( @@ -200,6 +230,21 @@ fn format_js_error_inner( ) .unwrap(); } + if !suggestions.is_empty() { + write!(s, "\n\n").unwrap(); + 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(), + }; + write!(s, "{}", suggestion.message).unwrap(); + if index != (suggestions.len() - 1) { + writeln!(s).unwrap(); + } + } + } + s } @@ -211,7 +256,21 @@ pub fn format_js_error(js_error: &JsError) -> String { index: 1, }); - format_js_error_inner(js_error, circular, true) + format_js_error_inner(js_error, circular, true, vec![]) +} + +/// Format a [`JsError`] for terminal output, printing additional suggestions. +pub fn format_js_error_with_suggestions( + js_error: &JsError, + suggestions: Vec<FixSuggestion>, +) -> String { + let circular = + find_recursive_cause(js_error).map(|reference| IndexedErrorReference { + reference, + index: 1, + }); + + format_js_error_inner(js_error, circular, true, suggestions) } #[cfg(test)] diff --git a/tests/specs/run/import_common_js/__test__.jsonc b/tests/specs/run/import_common_js/__test__.jsonc index a09929cdd..950c7f68c 100644 --- a/tests/specs/run/import_common_js/__test__.jsonc +++ b/tests/specs/run/import_common_js/__test__.jsonc @@ -1,6 +1,16 @@ { "steps": [ { "args": "run -R index.cjs", "output": "index.out" }, - { "args": "run -R main.ts", "output": "main.out" } + { "args": "run -R main.ts", "output": "main.out" }, + { + "args": "run module_error.js", + "output": "module_error.out", + "exitCode": 1 + }, + { + "args": "run exports_error.js", + "output": "exports_error.out", + "exitCode": 1 + } ] } diff --git a/tests/specs/run/import_common_js/exports_error.js b/tests/specs/run/import_common_js/exports_error.js new file mode 100644 index 000000000..a6a850fe2 --- /dev/null +++ b/tests/specs/run/import_common_js/exports_error.js @@ -0,0 +1 @@ +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/tests/specs/run/import_common_js/exports_error.out b/tests/specs/run/import_common_js/exports_error.out new file mode 100644 index 000000000..41eda2457 --- /dev/null +++ b/tests/specs/run/import_common_js/exports_error.out @@ -0,0 +1,7 @@ +error: Uncaught (in promise) ReferenceError: exports is not defined +Object.defineProperty(exports, "__esModule", { value: true }); + ^ + at [WILDCARD]exports_error.js:1:23 + + info: Deno does not support CommonJS modules without `.cjs` extension. + hint: Rewrite this module to ESM or change the file extension to `.cjs`. diff --git a/tests/specs/run/import_common_js/module_error.js b/tests/specs/run/import_common_js/module_error.js new file mode 100644 index 000000000..59a3cc0e7 --- /dev/null +++ b/tests/specs/run/import_common_js/module_error.js @@ -0,0 +1,3 @@ +module.exports = { + foobar: "foobar", +}; diff --git a/tests/specs/run/import_common_js/module_error.out b/tests/specs/run/import_common_js/module_error.out new file mode 100644 index 000000000..53a908d29 --- /dev/null +++ b/tests/specs/run/import_common_js/module_error.out @@ -0,0 +1,7 @@ +error: Uncaught (in promise) ReferenceError: module is not defined +module.exports = { +^ + at [WILDCARD]module_error.js:1:1 + + info: Deno does not support CommonJS modules without `.cjs` extension. + hint: Rewrite this module to ESM or change the file extension to `.cjs`. |