From 939b879d920a38b698e5a63ea74950321ebe5739 Mon Sep 17 00:00:00 2001 From: Jeff Charles Date: Tue, 20 Jun 2023 10:19:03 -0400 Subject: [PATCH] Add eval_module to JSContextRef --- crates/quickjs-wasm-rs/CHANGELOG.md | 1 + .../quickjs-wasm-rs/src/js_binding/context.rs | 86 +++++++++++++------ 2 files changed, 63 insertions(+), 24 deletions(-) diff --git a/crates/quickjs-wasm-rs/CHANGELOG.md b/crates/quickjs-wasm-rs/CHANGELOG.md index 1460daa7..643947ee 100644 --- a/crates/quickjs-wasm-rs/CHANGELOG.md +++ b/crates/quickjs-wasm-rs/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `JSValueRef` can convert to Rust types with `try_into` (previously this was implemented on `CallbackArg`) +- Added `eval_module` method on `JSContextRef` that evaluates JS code in a ECMAScript module scope ### Changed - Callback functions registered with `context.wrap_callback` now pass `JSValueRef` into the closure instead of `CallbackArg` diff --git a/crates/quickjs-wasm-rs/src/js_binding/context.rs b/crates/quickjs-wasm-rs/src/js_binding/context.rs index c125af7d..7eeccf3d 100644 --- a/crates/quickjs-wasm-rs/src/js_binding/context.rs +++ b/crates/quickjs-wasm-rs/src/js_binding/context.rs @@ -88,16 +88,53 @@ impl JSContextRef { /// context.eval_global("test.js", "1 + 1")?; /// ``` pub fn eval_global(&self, name: &str, contents: &str) -> Result { + self.eval(name, contents, EvalType::Global, false) + } + + /// Evaluates JavaScript code in an ECMAScript module scope. + /// + /// This method takes JavaScript code as a string and evaluates it in a + /// ECMAScript module scope. + /// + /// # Arguments + /// + /// * `name`: A string representing the name of the module. + /// * `contents`: The JavaScript code to be evaluated as a string. + /// + /// # Example + /// + /// ``` + /// let contenxt = JSContextRef::default(); + /// content.eval_module("test.js", "1 + 1")?; + /// ``` + pub fn eval_module(&self, name: &str, contents: &str) -> Result { + self.eval(name, contents, EvalType::Module, false) + } + + fn eval( + &self, + name: &str, + contents: &str, + eval_as: EvalType, + compile_only: bool, + ) -> Result { let input = CString::new(contents)?; let script_name = CString::new(name)?; let len = contents.len() - 1; + let mut eval_flags = match eval_as { + EvalType::Global => JS_EVAL_TYPE_GLOBAL, + EvalType::Module => JS_EVAL_TYPE_MODULE, + }; + if compile_only { + eval_flags |= JS_EVAL_FLAG_COMPILE_ONLY; + } let raw = unsafe { JS_Eval( self.inner, input.as_ptr(), len as _, script_name.as_ptr(), - JS_EVAL_TYPE_GLOBAL as i32, + eval_flags as i32, ) }; @@ -111,7 +148,7 @@ impl JSContextRef { /// * `name`: A string representing the name of the script. /// * `contents`: The JavaScript code to be compiled as a string. pub fn compile_module(&self, name: &str, contents: &str) -> Result> { - self.compile(name, contents, CompileType::Module) + self.compile(name, contents, EvalType::Module) } /// Compiles JavaScript to QuickJS bytecode with a global scope. @@ -121,35 +158,18 @@ impl JSContextRef { /// * `name`: A string representing the name of the script. /// * `contents`: The JavaScript code to be compiled as a string. pub fn compile_global(&self, name: &str, contents: &str) -> Result> { - self.compile(name, contents, CompileType::Global) + self.compile(name, contents, EvalType::Global) } - fn compile(&self, name: &str, contents: &str, compile_as: CompileType) -> Result> { - let input = CString::new(contents)?; - let script_name = CString::new(name)?; - let len = contents.len() - 1; - let compile_type = match compile_as { - CompileType::Global => JS_EVAL_TYPE_GLOBAL, - CompileType::Module => JS_EVAL_TYPE_MODULE, - }; - let raw = unsafe { - JS_Eval( - self.inner, - input.as_ptr(), - len as _, - script_name.as_ptr(), - (JS_EVAL_FLAG_COMPILE_ONLY | compile_type) as i32, - ) - }; - // returns err with details if JS_Eval fails - JSValueRef::new(self, raw)?; + fn compile(&self, name: &str, contents: &str, compile_as: EvalType) -> Result> { + let raw = self.eval(name, contents, compile_as, true)?; let mut output_size = 0; unsafe { let output_buffer = JS_WriteObject( self.inner, &mut output_size, - raw, + raw.value, JS_WRITE_OBJ_BYTECODE as i32, ); Ok(Vec::from_raw_parts( @@ -483,7 +503,7 @@ fn get_rust_value(raw: JSValue) -> Result<&'static RefCell> { } } -enum CompileType { +enum EvalType { Global, Module, } @@ -522,6 +542,24 @@ mod tests { Ok(()) } + #[test] + fn test_context_evaluates_code_in_a_module() -> Result<()> { + let ctx = JSContextRef::default(); + let contents = "export let a = 1;"; + let val = ctx.eval_module(SCRIPT_NAME, contents); + assert!(val.is_ok()); + Ok(()) + } + + #[test] + fn test_context_reports_invalid_module_code() -> Result<()> { + let ctx = JSContextRef::default(); + let contents = "a + 1 * z;"; + let val = ctx.eval_module(SCRIPT_NAME, contents); + assert!(val.is_err()); + Ok(()) + } + #[test] fn test_context_allows_access_to_global_object() -> Result<()> { let ctx = JSContextRef::default();