diff --git a/Cargo.lock b/Cargo.lock index 6051d356fca..c0c3079f36b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2459,6 +2459,7 @@ dependencies = [ "log", "nargo", "noirc_driver", + "noirc_errors", "noirc_frontend", "serde", "wasm-bindgen", diff --git a/compiler/wasm/Cargo.toml b/compiler/wasm/Cargo.toml index 47a0acdf8ac..b527e2de203 100644 --- a/compiler/wasm/Cargo.toml +++ b/compiler/wasm/Cargo.toml @@ -17,6 +17,7 @@ fm.workspace = true nargo.workspace = true noirc_driver.workspace = true noirc_frontend.workspace = true +noirc_errors.workspace = true wasm-bindgen.workspace = true serde.workspace = true js-sys.workspace = true diff --git a/compiler/wasm/src/compile.rs b/compiler/wasm/src/compile.rs index fbe6e761fa5..0f7baff4819 100644 --- a/compiler/wasm/src/compile.rs +++ b/compiler/wasm/src/compile.rs @@ -56,7 +56,9 @@ pub fn compile( if contracts.unwrap_or_default() { let compiled_contract = compile_contract(&mut context, crate_id, &compile_options) - .map_err(|_| JsCompileError::new("Failed to compile contract".to_string()))? + .map_err(|errs| { + JsCompileError::new("Failed to compile contract", errs, &context.file_manager) + })? .0; let optimized_contract = @@ -68,7 +70,9 @@ pub fn compile( Ok(::from_serde(&preprocessed_contract).unwrap()) } else { let compiled_program = compile_main(&mut context, crate_id, &compile_options, None, true) - .map_err(|_| JsCompileError::new("Failed to compile program".to_string()))? + .map_err(|errs| { + JsCompileError::new("Failed to compile program", errs, &context.file_manager) + })? .0; let optimized_program = diff --git a/compiler/wasm/src/errors.rs b/compiler/wasm/src/errors.rs index 6aa70dafa90..7090bf6f19f 100644 --- a/compiler/wasm/src/errors.rs +++ b/compiler/wasm/src/errors.rs @@ -1,29 +1,82 @@ -use js_sys::JsString; - +use gloo_utils::format::JsValueSerdeExt; +use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; +use fm::FileManager; +use noirc_errors::FileDiagnostic; + #[wasm_bindgen(typescript_custom_section)] -const COMPILE_ERROR: &'static str = r#" -export type CompileError = Error; +const DIAGNOSTICS: &'static str = r#" +export type Diagnostic = { + message: string; + file_path: string; + secondaries: ReadonlyArray<{ + message: string; + start: number; + end: number; + }>; +} + +interface CompileError { + diagnostics: ReadonlyArray; +} "#; -/// `CompileError` is a raw js error. -/// It'd be ideal that `CompileError` was a subclass of `Error`, but for that we'd need to use JS snippets or a js module. -/// Currently JS snippets don't work with a nodejs target. And a module would be too much for just a custom error type. -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(extends = js_sys::Error, js_name = "CompileError", typescript_type = "CompileError")] - #[derive(Clone, Debug, PartialEq, Eq)] - pub type JsCompileError; - - #[wasm_bindgen(constructor, js_class = "Error")] - fn constructor(message: JsString) -> JsCompileError; +#[derive(Serialize, Deserialize)] +struct JsDiagnosticLabel { + message: String, + start: u32, + end: u32, +} + +#[derive(Serialize, Deserialize)] +struct JsDiagnostic { + message: String, + file_path: String, + secondaries: Vec, +} + +impl JsDiagnostic { + fn new(file_diagnostic: &FileDiagnostic, file_path: String) -> JsDiagnostic { + let diagnostic = &file_diagnostic.diagnostic; + let message = diagnostic.message.clone(); + + let secondaries = diagnostic + .secondaries + .iter() + .map(|label| JsDiagnosticLabel { + message: label.message.clone(), + start: label.span.start(), + end: label.span.end(), + }) + .collect(); + + JsDiagnostic { message, file_path, secondaries } + } +} + +#[wasm_bindgen(getter_with_clone, js_name = "CompileError")] +pub struct JsCompileError { + pub message: js_sys::JsString, + pub diagnostics: JsValue, } impl JsCompileError { - /// Creates a new execution error with the given call stack. - /// Call stacks won't be optional in the future, after removing ErrorLocation in ACVM. - pub fn new(message: String) -> Self { - JsCompileError::constructor(JsString::from(message)) + pub fn new( + message: &str, + file_diagnostics: Vec, + file_manager: &FileManager, + ) -> JsCompileError { + let diagnostics: Vec<_> = file_diagnostics + .iter() + .map(|err| { + JsDiagnostic::new(err, file_manager.path(err.file_id).to_str().unwrap().to_string()) + }) + .collect(); + + JsCompileError { + message: js_sys::JsString::from(message.to_string()), + diagnostics: ::from_serde(&diagnostics).unwrap(), + } } }