From f09a562309bca45f13cc77ec48d2c743e216d686 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Sun, 13 Sep 2026 13:51:43 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20CodeRabbit=20Chat:=20Add=20confi?= =?UTF-8?q?gurable=20diagnostic=20links=20to=20VM=20error=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rust/src/interfaces/interface.rs | 43 +++++++++++++++++++---- rust/src/interfaces/napi_interface.rs | 27 ++++++++++++++ rust/src/interfaces/native_interface.rs | 19 ++++++++++ rust/src/interfaces/wasm_interface.rs | 22 ++++++++++-- rust/src/modules/vmerror/config.rs | 6 +++- rust/src/modules/vmerror/display.rs | 13 ++++--- rust/src/modules/vmerror/error.rs | 20 +++++++---- rust/src/types/error_options.rs | 12 +++++++ rust/src/types/js/js_error_options.rs | 2 ++ rust/src/types/wasm/wasm_error_options.rs | 2 ++ 10 files changed, 144 insertions(+), 22 deletions(-) diff --git a/rust/src/interfaces/interface.rs b/rust/src/interfaces/interface.rs index 2b06b0ff..7844c67d 100644 --- a/rust/src/interfaces/interface.rs +++ b/rust/src/interfaces/interface.rs @@ -74,6 +74,7 @@ pub struct LightVM { pub backtrace: bool, pub explain: bool, pub hint: bool, + pub diagnostic_links: bool, } impl LightVM { pub fn new_node( @@ -82,6 +83,7 @@ impl LightVM { backtrace: bool, explain: bool, hint: bool, + diagnostic_links: bool, ) -> Self { use crate::types::value::Value; use crate::types::vmstate::VmState; @@ -114,6 +116,7 @@ impl LightVM { backtrace, explain, hint, + diagnostic_links, } } #[inline(always)] @@ -126,8 +129,13 @@ impl LightVM { } Ok(()) } - pub fn set_mode(&self, backtrace: bool, explain: bool, hint: bool) { - crate::modules::vmerror::config::set_thread_error_config(backtrace, explain, hint); + pub fn set_mode(&self, backtrace: bool, explain: bool, hint: bool, diagnostic_links: bool) { + crate::modules::vmerror::config::set_thread_error_config( + backtrace, + explain, + hint, + diagnostic_links, + ); } pub fn index_metadata(&mut self) { self.functions.clear(); @@ -169,7 +177,12 @@ impl LightVM { get_versions() } pub fn load_internal(&mut self, source: String) -> Result<(), VMError> { - self.set_mode(self.backtrace, self.explain, self.hint); + self.set_mode( + self.backtrace, + self.explain, + self.hint, + self.diagnostic_links, + ); crate::modules::vmerror::get_backtrace::clear_backtrace(); if self.backtrace { crate::modules::vmerror::get_backtrace::capture_backtrace(); @@ -229,7 +242,12 @@ impl LightVM { Ok(()) } pub fn run_internal(&mut self, options: Option) -> Result { - self.set_mode(self.backtrace, self.explain, self.hint); + self.set_mode( + self.backtrace, + self.explain, + self.hint, + self.diagnostic_links, + ); crate::modules::vmerror::get_backtrace::clear_backtrace(); if self.backtrace { crate::modules::vmerror::get_backtrace::capture_backtrace(); @@ -280,7 +298,12 @@ impl LightVM { } #[inline] pub fn compile_internal(&mut self, config: CompileConfig) -> Result<(), VMError> { - self.set_mode(self.backtrace, self.explain, self.hint); + self.set_mode( + self.backtrace, + self.explain, + self.hint, + self.diagnostic_links, + ); crate::modules::vmerror::get_backtrace::clear_backtrace(); if self.backtrace { crate::modules::vmerror::get_backtrace::capture_backtrace(); @@ -498,7 +521,12 @@ impl LightVM { bytecode_raw: serde_json::Value, ) -> Result { self.require(Capability::Control)?; - self.set_mode(self.backtrace, self.explain, self.hint); + self.set_mode( + self.backtrace, + self.explain, + self.hint, + self.diagnostic_links, + ); crate::modules::vmerror::get_backtrace::clear_backtrace(); if self.backtrace { crate::modules::vmerror::get_backtrace::capture_backtrace(); @@ -642,6 +670,7 @@ mod tests { backtrace: false, explain: false, hint: true, + diagnostic_links: true, } } #[test] @@ -904,7 +933,7 @@ mod tests { vm.backtrace = true; vm.explain = true; vm.hint = false; - set_thread_error_config(false, false, true); + set_thread_error_config(false, false, true, true); let before = get_thread_or_global_config(); let result = vm.optimize_bytecode_internal(serde_json::json!([["noop"]])); let after = get_thread_or_global_config(); diff --git a/rust/src/interfaces/napi_interface.rs b/rust/src/interfaces/napi_interface.rs index a7b3092e..aede8e80 100644 --- a/rust/src/interfaces/napi_interface.rs +++ b/rust/src/interfaces/napi_interface.rs @@ -103,6 +103,7 @@ impl NodeLightVM { backtrace: error_options.backtrace.unwrap_or(false), explain: error_options.explain.unwrap_or(false), hint: error_options.hint.unwrap_or(true), + diagnostic_links: error_options.diagnostic_links.unwrap_or(true), }, }) } @@ -187,6 +188,11 @@ impl NodeLightVM { self.inner.hint = enabled; Ok(()) } + #[napi(js_name = "withDiagnosticLinks")] + pub fn with_diagnostic_links(&mut self, enabled: bool) -> Result<()> { + self.inner.diagnostic_links = enabled; + Ok(()) + } #[napi] pub fn info(&mut self) -> Result { let info_vm = self.inner.info_internal(); @@ -561,6 +567,7 @@ impl NodeLightVM { is_backtrace, is_explain, is_hint, + self.inner.diagnostic_links, ); vm_instance.caps = self .inner @@ -599,6 +606,7 @@ impl NodeLightVM { #[cfg(test)] mod tests { use super::*; + use crate::types::js::js_error_options::JSErrorOptions; #[test] fn unknown_capability_uses_vm_error_display() { let config = VmNapiConfig { @@ -717,4 +725,23 @@ mod tests { r#"[["stop"]]"# ); } + #[test] + fn diagnostic_links_can_be_configured_and_updated() { + let mut vm = NodeLightVM::napi_new(VmNapiConfig { + error_options: Some(JSErrorOptions { + diagnostic_links: Some(false), + ..Default::default() + }), + ..Default::default() + }) + .expect("expected a VM"); + + assert!(!vm.inner.diagnostic_links); + vm.with_diagnostic_links(true) + .expect("expected the setting to update"); + assert!(vm.inner.diagnostic_links); + vm.with_diagnostic_links(false) + .expect("expected the setting to update"); + assert!(!vm.inner.diagnostic_links); + } } diff --git a/rust/src/interfaces/native_interface.rs b/rust/src/interfaces/native_interface.rs index 4eca12d1..683ffd6c 100644 --- a/rust/src/interfaces/native_interface.rs +++ b/rust/src/interfaces/native_interface.rs @@ -225,6 +225,7 @@ impl LightVM { backtrace: error_options.backtrace, explain: error_options.explain, hint: error_options.hint, + diagnostic_links: error_options.diagnostic_links, } } pub fn set_max_io(mut self, value: usize) -> Self { @@ -283,6 +284,10 @@ impl LightVM { self.hint = enabled; self } + pub fn with_diagnostic_links(mut self, enabled: bool) -> Self { + self.diagnostic_links = enabled; + self + } #[cfg(not(feature = "wasm"))] pub fn info(&mut self) -> InfoVM { self.info_internal() @@ -464,6 +469,7 @@ impl LightVM { backtrace: self.backtrace, explain: self.explain, hint: self.hint, + diagnostic_links: self.diagnostic_links, time_budget: self.time_budget, can_control: self.caps.contains(&Capability::Control), can_debug: self.caps.contains(&Capability::Debug), @@ -475,6 +481,7 @@ pub struct LightVMTools { pub backtrace: bool, pub explain: bool, pub hint: bool, + pub diagnostic_links: bool, pub time_budget: TimeBudget, pub can_control: bool, pub can_debug: bool, @@ -534,6 +541,7 @@ impl LightVMTools { backtrace: self.backtrace, explain: self.explain, hint: self.hint, + diagnostic_links: self.diagnostic_links, }), }; let opt_str = LightVM::new(config) @@ -628,6 +636,15 @@ mod tests { assert_eq!(vm.state, VmState::Idle); } #[test] + fn diagnostic_links_can_be_disabled() { + let mut vm = LightVM::new(VmConfig::default()).with_diagnostic_links(false); + let error = vm + .load_internal("invalid source".to_string()) + .expect_err("expected invalid source to fail"); + + assert!(!error.to_string().contains("documentation:")); + } + #[test] fn on_registers_listener() { let config = VmConfig { caps: vec![], @@ -818,6 +835,7 @@ mod tests { backtrace: tools.backtrace, explain: tools.explain, hint: tools.hint, + diagnostic_links: tools.diagnostic_links, }), ..Default::default() }); @@ -845,6 +863,7 @@ mod tests { backtrace: tools.backtrace, explain: tools.explain, hint: tools.hint, + diagnostic_links: tools.diagnostic_links, }), ..Default::default() }); diff --git a/rust/src/interfaces/wasm_interface.rs b/rust/src/interfaces/wasm_interface.rs index 60b71c55..82873b5e 100644 --- a/rust/src/interfaces/wasm_interface.rs +++ b/rust/src/interfaces/wasm_interface.rs @@ -102,6 +102,7 @@ impl WasmLightVM { backtrace: error_options.backtrace.unwrap_or(false), explain: error_options.explain.unwrap_or(false), hint: error_options.hint.unwrap_or(true), + diagnostic_links: error_options.diagnostic_links.unwrap_or(true), }, }) } @@ -173,6 +174,10 @@ impl WasmLightVM { pub fn with_hint(&mut self, enabled: bool) { self.inner.hint = enabled; } + #[wasm_bindgen(js_name = "withDiagnosticLinks")] + pub fn with_diagnostic_links(&mut self, enabled: bool) { + self.inner.diagnostic_links = enabled; + } #[wasm_bindgen] pub fn load(&mut self, source: String) -> Result<(), JsValue> { self @@ -384,6 +389,7 @@ impl WasmLightVM { backtrace: self.inner.backtrace, explain: self.inner.explain, hint: self.inner.hint, + diagnostic_links: self.inner.diagnostic_links, time_budget: self.inner.time_budget, can_observe: self.inner.caps.contains(&Capability::Observe), can_control: self.inner.caps.contains(&Capability::Control), @@ -398,6 +404,7 @@ pub struct WasmLightVMTools { pub backtrace: bool, pub explain: bool, pub hint: bool, + pub diagnostic_links: bool, time_budget: TimeBudget, pub can_observe: bool, pub can_control: bool, @@ -417,6 +424,7 @@ impl WasmLightVMTools { self.backtrace, self.explain, self.hint, + self.diagnostic_links, ); vm_instance.caps = { let mut caps = HashSet::new(); @@ -506,7 +514,7 @@ mod tests { use super::*; use crate::types::security_config::SecurityConfig; fn vm_with_control_capability() -> WasmLightVM { - let mut inner = LightVM::new_node(SecurityConfig::default(), false, false, false, true); + let mut inner = LightVM::new_node(SecurityConfig::default(), false, false, false, true, true); inner.caps.insert(Capability::Control); WasmLightVM { inner } } @@ -515,10 +523,14 @@ mod tests { let json_data = serde_json::json!({ "caps": [0, 2], "runtimeConfig": { "nightly": true }, - "errorOptions": { "hint": true } + "errorOptions": { "hint": true, "diagnosticLinks": false } }); let config: VmWasmConfig = serde_json::from_value(json_data).unwrap(); assert_eq!(config.caps, vec![0, 2]); + assert_eq!( + config.error_options.as_ref().unwrap().diagnostic_links, + Some(false) + ); #[cfg(target_arch = "wasm32")] { let mut vm = WasmLightVM::new(serde_wasm_bindgen::to_value(&config).unwrap()).unwrap(); @@ -530,6 +542,12 @@ mod tests { assert_eq!(config.runtime_config.unwrap().nightly, Some(true)); } #[test] + fn diagnostic_links_can_be_updated() { + let mut vm = vm_with_control_capability(); + vm.with_diagnostic_links(false); + assert!(!vm.inner.diagnostic_links); + } + #[test] fn start_and_finish_event_names_are_supported() { assert_eq!(parse_event("start"), Some(VmEvent::Start)); assert_eq!(parse_event("finish"), Some(VmEvent::Finish)); diff --git a/rust/src/modules/vmerror/config.rs b/rust/src/modules/vmerror/config.rs index 2e3b1f57..34dc2aca 100644 --- a/rust/src/modules/vmerror/config.rs +++ b/rust/src/modules/vmerror/config.rs @@ -15,6 +15,7 @@ pub struct VMErrorContainer { pub backtrace: bool, pub explain: bool, pub hint: bool, + pub diagnostic_links: bool, } impl Default for VMErrorContainer { fn default() -> Self { @@ -27,6 +28,7 @@ impl VMErrorContainer { backtrace: false, explain: false, hint: true, + diagnostic_links: true, } } pub fn get_value(&self) -> VMErrorContainer { @@ -34,6 +36,7 @@ impl VMErrorContainer { backtrace: self.backtrace, explain: self.explain, hint: self.hint, + diagnostic_links: self.diagnostic_links, } } } @@ -41,12 +44,13 @@ thread_local! { static THREAD_ERROR_CONFIG: RefCell> = const { RefCell::new(None) }; } static EXPLAIN_MODE: OnceLock> = OnceLock::new(); -pub fn set_thread_error_config(backtrace: bool, explain: bool, hint: bool) { +pub fn set_thread_error_config(backtrace: bool, explain: bool, hint: bool, diagnostic_links: bool) { THREAD_ERROR_CONFIG.with(|config| { *config.borrow_mut() = Some(VMErrorContainer { backtrace, explain, hint, + diagnostic_links, }); }); } diff --git a/rust/src/modules/vmerror/display.rs b/rust/src/modules/vmerror/display.rs index a54b1bb4..2a1021fe 100644 --- a/rust/src/modules/vmerror/display.rs +++ b/rust/src/modules/vmerror/display.rs @@ -22,6 +22,7 @@ impl fmt::Display for VMError { let is_backtrace = config.backtrace; let is_explain = config.explain; let is_hint = config.hint; + let diagnostic_links = config.diagnostic_links; let err_type = match self { VMError::StackOverflow { .. } => "StackOverflow", VMError::StackUnderflow { .. } => "StackUnderflow", @@ -143,11 +144,13 @@ impl fmt::Display for VMError { write!(f, "\n {DARK_GRAY}error type: {}", err_type)?; } } - write!( - f, - "\n {RESET}{CYAN}├── {DARK_GRAY}documentation: {}{RESET}", - self.diagnostic_link() - )?; + if diagnostic_links { + write!( + f, + "\n {RESET}{CYAN}├── {DARK_GRAY}documentation: {}{RESET}", + self.diagnostic_link() + )?; + } if is_backtrace { let backtrace = get_backtrace(); write!( diff --git a/rust/src/modules/vmerror/error.rs b/rust/src/modules/vmerror/error.rs index 230fc6c2..f215cde1 100644 --- a/rust/src/modules/vmerror/error.rs +++ b/rust/src/modules/vmerror/error.rs @@ -182,16 +182,14 @@ mod tests { #[test] fn formatted_error_contains_its_diagnostic_link() { - set_thread_error_config(false, false, true); + set_thread_error_config(false, false, true, true); let error = VMError::StackOverflow { ip: 1, limit: 2 }; let formatted = error.to_string(); let metadata_position = formatted.find("error type:").unwrap(); let documentation_position = formatted.find("documentation:").unwrap(); let hint_position = formatted.find("hint:").unwrap(); - assert!( - formatted.contains(&format!("documentation: {}", error.diagnostic_link())) - ); + assert!(formatted.contains(&format!("documentation: {}", error.diagnostic_link()))); assert!(formatted.contains("\x1b[36m├── \x1b[2;37mdocumentation:")); assert!(metadata_position < documentation_position); assert!(documentation_position < hint_position); @@ -199,7 +197,7 @@ mod tests { #[test] fn formatted_system_error_contains_its_diagnostic_link() { - set_thread_error_config(false, false, true); + set_thread_error_config(false, false, true, true); let error = VMError::SystemError(SmolStr::new("system failure")); let formatted = error.to_string(); let error_position = formatted.find("system failure").unwrap(); @@ -213,7 +211,7 @@ mod tests { #[test] fn diagnostic_link_is_rendered_when_hints_are_disabled() { - set_thread_error_config(false, false, false); + set_thread_error_config(false, false, false, true); let error = VMError::StackOverflow { ip: 1, limit: 2 }; let formatted = error.to_string(); @@ -223,7 +221,7 @@ mod tests { #[test] fn diagnostic_link_is_rendered_before_backtrace() { - set_thread_error_config(true, false, true); + set_thread_error_config(true, false, true, true); let error = VMError::StackOverflow { ip: 1, limit: 2 }; let formatted = error.to_string(); let documentation_position = formatted.find("documentation:").unwrap(); @@ -231,4 +229,12 @@ mod tests { assert!(documentation_position < backtrace_position); } + + #[test] + fn diagnostic_link_can_be_disabled() { + set_thread_error_config(false, false, true, false); + let error = VMError::StackOverflow { ip: 1, limit: 2 }; + + assert!(!error.to_string().contains("documentation:")); + } } diff --git a/rust/src/types/error_options.rs b/rust/src/types/error_options.rs index 1445e34d..202ad519 100644 --- a/rust/src/types/error_options.rs +++ b/rust/src/types/error_options.rs @@ -13,6 +13,7 @@ pub struct ErrorOptions { pub backtrace: bool, pub explain: bool, pub hint: bool, + pub diagnostic_links: bool, } impl Default for ErrorOptions { fn default() -> Self { @@ -20,6 +21,17 @@ impl Default for ErrorOptions { backtrace: false, explain: false, hint: true, + diagnostic_links: true, } } } + +#[cfg(test)] +mod tests { + use super::ErrorOptions; + + #[test] + fn diagnostic_links_are_enabled_by_default() { + assert!(ErrorOptions::default().diagnostic_links); + } +} diff --git a/rust/src/types/js/js_error_options.rs b/rust/src/types/js/js_error_options.rs index 52e7f919..28470507 100644 --- a/rust/src/types/js/js_error_options.rs +++ b/rust/src/types/js/js_error_options.rs @@ -18,4 +18,6 @@ pub struct JSErrorOptions { pub backtrace: Option, pub explain: Option, pub hint: Option, + #[ts(rename = "diagnosticLinks")] + pub diagnostic_links: Option, } diff --git a/rust/src/types/wasm/wasm_error_options.rs b/rust/src/types/wasm/wasm_error_options.rs index 031f8044..74dd03b1 100644 --- a/rust/src/types/wasm/wasm_error_options.rs +++ b/rust/src/types/wasm/wasm_error_options.rs @@ -12,8 +12,10 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "wasm")] #[derive(Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct WASMErrorOptions { pub backtrace: Option, pub explain: Option, pub hint: Option, + pub diagnostic_links: Option, }