use std::{
borrow::Cow,
collections::HashSet,
ffi::{CStr, CString},
fmt,
mem::MaybeUninit,
ptr::{self, NonNull},
slice,
};
#[cfg(feature = "exports")]
use std::marker::PhantomData;
use crate::{qjs, Atom, Context, Ctx, Error, FromAtom, FromJs, IntoJs, Result, Value};
#[macro_export]
macro_rules! module_init {
($type:ty) => {
$crate::module_init!(js_init_module: $type);
};
($name:ident: $type:ty) => {
#[no_mangle]
pub unsafe extern "C" fn $name(
ctx: *mut $crate::qjs::JSContext,
module_name: *const $crate::qjs::c_char,
) -> *mut $crate::qjs::JSModuleDef {
$crate::Module::init_raw::<$type>(ctx, module_name)
}
};
}
pub type ModuleLoadFn =
unsafe extern "C" fn(*mut qjs::JSContext, *const qjs::c_char) -> *mut qjs::JSModuleDef;
#[derive(Clone)]
pub enum ModuleDataKind {
Source(Vec<u8>),
Native(for<'js> unsafe fn(ctx: &Ctx<'js>, name: Vec<u8>) -> Result<Module<'js>>),
Raw(ModuleLoadFn),
ByteCode(Cow<'static, [u8]>),
}
impl fmt::Debug for ModuleDataKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ModuleDataKind::Source(ref x) => {
f.debug_tuple("ModuleDataKind::Source").field(x).finish()
}
ModuleDataKind::Raw(ref x) => f.debug_tuple("ModuleDataKind::Raw").field(x).finish(),
ModuleDataKind::ByteCode(ref x) => {
f.debug_tuple("ModuleDataKind::ByteCode").field(x).finish()
}
ModuleDataKind::Native(_) => f
.debug_tuple("ModuleDataKind::ByteCode")
.field(&"<native function>")
.finish(),
}
}
}
impl ModuleDataKind {
unsafe fn declare<'js, N: Into<Vec<u8>>>(self, ctx: Ctx<'js>, name: N) -> Result<Module<'js>> {
match self {
ModuleDataKind::Source(x) => Module::unsafe_declare(ctx, name, x),
ModuleDataKind::Native(x) => (x)(&ctx, name.into()),
ModuleDataKind::Raw(x) => {
let name = CString::new(name)?;
let ptr = (x)(ctx.as_ptr(), name.as_ptr().cast());
let ptr = NonNull::new(ptr).ok_or(Error::Unknown)?;
Ok(Module::from_module_def(ctx, ptr))
}
ModuleDataKind::ByteCode(x) => Module::unsafe_declare_read_object(ctx, x.as_ref()),
}
}
}
#[derive(Clone, Debug)]
pub struct ModuleData {
name: Vec<u8>,
data: ModuleDataKind,
}
impl ModuleData {
pub fn source<N, S>(name: N, source: S) -> Self
where
N: Into<Vec<u8>>,
S: Into<Vec<u8>>,
{
ModuleData {
name: name.into(),
data: ModuleDataKind::Source(source.into()),
}
}
pub unsafe fn bytecode<N, S>(name: N, bytecode: S) -> Self
where
N: Into<Vec<u8>>,
S: Into<Cow<'static, [u8]>>,
{
ModuleData {
name: name.into(),
data: ModuleDataKind::ByteCode(bytecode.into()),
}
}
pub fn native<D, N>(name: N) -> Self
where
D: ModuleDef,
N: Into<Vec<u8>>,
{
unsafe fn define<'js, D: ModuleDef>(ctx: &Ctx<'js>, name: Vec<u8>) -> Result<Module<'js>> {
Module::unsafe_declare_def::<D, _>(ctx.clone(), name)
}
ModuleData {
name: name.into(),
data: ModuleDataKind::Native(define::<D>),
}
}
pub unsafe fn raw<N>(name: N, load_fn: ModuleLoadFn) -> Self
where
N: Into<Vec<u8>>,
{
ModuleData {
name: name.into(),
data: ModuleDataKind::Raw(load_fn),
}
}
pub fn kind(&self) -> &ModuleDataKind {
&self.data
}
pub fn declare<'js>(self, ctx: Ctx<'js>) -> Result<()> {
unsafe {
let _ = self.unsafe_declare(ctx)?;
}
Ok(())
}
pub unsafe fn unsafe_declare<'js>(self, ctx: Ctx<'js>) -> Result<Module<'js>> {
self.data.declare(ctx, self.name)
}
}
pub struct ModulesBuilder {
modules: Vec<ModuleData>,
}
impl ModulesBuilder {
pub fn new() -> Self {
ModulesBuilder {
modules: Vec::new(),
}
}
pub fn source<N, S>(mut self, name: N, source: S) -> Self
where
N: Into<Vec<u8>>,
S: Into<Vec<u8>>,
{
self.with_source(name, source);
self
}
pub fn with_source<N, S>(&mut self, name: N, source: S) -> &mut Self
where
N: Into<Vec<u8>>,
S: Into<Vec<u8>>,
{
self.modules.push(ModuleData::source(name, source));
self
}
pub fn native<D, N>(mut self, name: N) -> Self
where
D: ModuleDef,
N: Into<Vec<u8>>,
{
self.with_native::<D, _>(name);
self
}
pub fn with_native<D, N>(&mut self, name: N) -> &mut Self
where
D: ModuleDef,
N: Into<Vec<u8>>,
{
self.modules.push(ModuleData::native::<D, N>(name));
self
}
pub fn eval<'js>(self, ctx: &Ctx<'js>) -> Result<Vec<Module<'js>>> {
let mut modules = Vec::with_capacity(self.modules.len());
for m in self
.modules
.into_iter()
.map(|x| unsafe { x.unsafe_declare(ctx.clone()) })
{
modules.push(m?);
}
for m in modules.iter() {
unsafe { m.eval()? }
}
Ok(modules)
}
}
impl Default for ModulesBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct Declarations {
declarations: HashSet<Cow<'static, CStr>>,
}
impl Declarations {
pub(crate) fn new() -> Self {
Declarations {
declarations: HashSet::new(),
}
}
pub fn declare<N>(&mut self, name: N) -> Result<&mut Self>
where
N: Into<Vec<u8>>,
{
self.declarations.insert(Cow::Owned(CString::new(name)?));
Ok(self)
}
pub fn declare_static(&mut self, name: &'static CStr) -> Result<&mut Self> {
self.declarations.insert(Cow::Borrowed(name));
Ok(self)
}
pub(crate) unsafe fn apply(self, ctx: Ctx<'_>, module: &Module) -> Result<()> {
for k in self.declarations {
let ptr = match k {
Cow::Borrowed(x) => x.as_ptr(),
Cow::Owned(x) => x.into_raw(),
};
let res = unsafe {
qjs::JS_AddModuleExport(ctx.as_ptr(), module.as_module_def().as_ptr(), ptr)
};
if res < 0 {
return Err(Error::Allocation);
}
}
Ok(())
}
pub fn iter(&self) -> impl Iterator<Item = &Cow<'static, CStr>> {
self.declarations.iter()
}
}
struct Export<'js> {
name: CString,
value: Value<'js>,
}
pub struct Exports<'js> {
ctx: Ctx<'js>,
exports: Vec<Export<'js>>,
}
impl<'js> Exports<'js> {
pub(crate) fn new(ctx: Ctx<'js>) -> Self {
Exports {
ctx,
exports: Vec::new(),
}
}
pub fn export<N: Into<Vec<u8>>, T: IntoJs<'js>>(
&mut self,
name: N,
value: T,
) -> Result<&mut Self> {
let name = CString::new(name.into())?;
let value = value.into_js(&self.ctx)?;
self.export_value(name, value)
}
pub fn export_value(&mut self, name: CString, value: Value<'js>) -> Result<&mut Self> {
self.exports.push(Export { name, value });
Ok(self)
}
pub(crate) unsafe fn apply(self, module: Module) -> Result<()> {
for export in self.exports {
let name = export.name;
let value = export.value;
let res = unsafe {
qjs::JS_SetModuleExport(
self.ctx.as_ptr(),
module.as_module_def().as_ptr(),
name.as_ref().as_ptr(),
value.into_js_value(),
)
};
if res < 0 {
return Err(Error::Unknown);
}
}
Ok(())
}
pub fn iter(&self) -> impl Iterator<Item = (&CStr, &Value<'js>)> {
self.exports.iter().map(|x| (x.name.as_c_str(), &x.value))
}
}
#[derive(Clone)]
#[must_use = "if access to the module object is not required, prefer to only declare a module"]
pub struct Module<'js> {
ctx: Ctx<'js>,
module: NonNull<qjs::JSModuleDef>,
}
pub trait ModuleDef {
fn declare(declare: &mut Declarations) -> Result<()> {
let _ = declare;
Ok(())
}
fn evaluate<'js>(_ctx: &Ctx<'js>, exports: &mut Exports<'js>) -> Result<()> {
let _ = exports;
Ok(())
}
}
impl<'js> Module<'js> {
pub(crate) fn from_module_def(ctx: Ctx<'js>, def: NonNull<qjs::JSModuleDef>) -> Self {
Module { ctx, module: def }
}
pub(crate) fn as_module_def(&self) -> NonNull<qjs::JSModuleDef> {
self.module
}
pub fn declare<N, S>(ctx: Ctx<'js>, name: N, source: S) -> Result<()>
where
N: Into<Vec<u8>>,
S: Into<Vec<u8>>,
{
unsafe {
let _ = Self::unsafe_declare(ctx, name, source)?;
}
Ok(())
}
pub fn evaluate<N, S>(ctx: Ctx<'js>, name: N, source: S) -> Result<Module<'js>>
where
N: Into<Vec<u8>>,
S: Into<Vec<u8>>,
{
let module = unsafe { Self::unsafe_declare(ctx, name, source)? };
unsafe { module.eval()? };
Ok(module)
}
pub fn declare_def<D, N>(ctx: Ctx<'js>, name: N) -> Result<()>
where
N: Into<Vec<u8>>,
D: ModuleDef,
{
unsafe {
let _ = Self::unsafe_declare_def::<D, _>(ctx, name)?;
}
Ok(())
}
pub fn evaluate_def<D, N>(ctx: Ctx<'js>, name: N) -> Result<Module<'js>>
where
N: Into<Vec<u8>>,
D: ModuleDef,
{
let module = unsafe { Self::unsafe_declare_def::<D, _>(ctx, name)? };
unsafe { module.eval()? };
Ok(module)
}
pub fn name<N>(&self) -> Result<N>
where
N: FromAtom<'js>,
{
let name = unsafe {
Atom::from_atom_val(
self.ctx.clone(),
qjs::JS_GetModuleName(self.ctx.as_ptr(), self.as_module_def().as_ptr()),
)
};
N::from_atom(name)
}
pub fn meta<T>(&self) -> Result<T>
where
T: FromJs<'js>,
{
let meta = unsafe {
Value::from_js_value(
self.ctx.clone(),
self.ctx.handle_exception(qjs::JS_GetImportMeta(
self.ctx.as_ptr(),
self.as_module_def().as_ptr(),
))?,
)
};
T::from_js(&self.ctx, meta)
}
pub fn write_object_le(&self) -> Result<Vec<u8>> {
let swap = cfg!(target_endian = "big");
self.write_object(swap)
}
pub fn write_object_be(&self) -> Result<Vec<u8>> {
let swap = cfg!(target_endian = "little");
self.write_object(swap)
}
pub unsafe extern "C" fn init_raw<D>(
ctx: *mut qjs::JSContext,
name: *const qjs::c_char,
) -> *mut qjs::JSModuleDef
where
D: ModuleDef,
{
Context::init_raw(ctx);
let ctx = Ctx::from_ptr(ctx);
let name = CStr::from_ptr(name).to_bytes();
match Self::unsafe_declare_def::<D, _>(ctx.clone(), name) {
Ok(module) => module.as_module_def().as_ptr(),
Err(error) => {
error.throw(&ctx);
ptr::null_mut()
}
}
}
pub fn write_object(&self, swap_endianess: bool) -> Result<Vec<u8>> {
let ctx = &self.ctx;
let mut len = MaybeUninit::uninit();
let mut flags = qjs::JS_WRITE_OBJ_BYTECODE;
if swap_endianess {
flags |= qjs::JS_WRITE_OBJ_BSWAP;
}
let value = qjs::JS_MKPTR(qjs::JS_TAG_MODULE, self.module.as_ptr().cast());
let buf =
unsafe { qjs::JS_WriteObject(ctx.as_ptr(), len.as_mut_ptr(), value, flags as i32) };
if buf.is_null() {
return Err(ctx.raise_exception());
}
let len = unsafe { len.assume_init() };
let obj = unsafe { slice::from_raw_parts(buf, len as _) };
let obj = Vec::from(obj);
unsafe { qjs::js_free(ctx.as_ptr(), buf as _) };
Ok(obj)
}
pub fn declare_read_object(ctx: Ctx<'js>, bytes: &[u8]) -> Result<()> {
unsafe {
let _ = Self::unsafe_declare_read_object(ctx, bytes)?;
}
Ok(())
}
pub fn instantiate_read_object(ctx: Ctx<'js>, bytes: &[u8]) -> Result<Module<'js>> {
let module = unsafe { Self::unsafe_declare_read_object(ctx, bytes)? };
unsafe {
module.eval()?;
}
Ok(module)
}
pub unsafe fn unsafe_declare_read_object(ctx: Ctx<'js>, bytes: &[u8]) -> Result<Module<'js>> {
let module = unsafe {
qjs::JS_ReadObject(
ctx.as_ptr(),
bytes.as_ptr(),
bytes.len() as _,
(qjs::JS_READ_OBJ_BYTECODE | qjs::JS_READ_OBJ_ROM_DATA) as i32,
)
};
let module = ctx.handle_exception(module)?;
debug_assert_eq!(qjs::JS_TAG_MODULE, unsafe { qjs::JS_VALUE_GET_TAG(module) });
let module = qjs::JS_VALUE_GET_PTR(module).cast::<qjs::JSModuleDef>();
let module = NonNull::new(module).unwrap();
Ok(Module { ctx, module })
}
pub unsafe fn unsafe_declare<N, S>(ctx: Ctx<'js>, name: N, source: S) -> Result<Module<'js>>
where
N: Into<Vec<u8>>,
S: Into<Vec<u8>>,
{
let name = CString::new(name)?;
let flag =
qjs::JS_EVAL_TYPE_MODULE | qjs::JS_EVAL_FLAG_STRICT | qjs::JS_EVAL_FLAG_COMPILE_ONLY;
let module = unsafe { ctx.eval_raw(source, name.as_c_str(), flag as i32)? };
let module = ctx.handle_exception(module)?;
debug_assert_eq!(qjs::JS_TAG_MODULE, unsafe { qjs::JS_VALUE_GET_TAG(module) });
let module = qjs::JS_VALUE_GET_PTR(module).cast::<qjs::JSModuleDef>();
let module = NonNull::new(module).unwrap();
Ok(Module { ctx, module })
}
pub unsafe fn unsafe_declare_def<D, N>(ctx: Ctx<'js>, name: N) -> Result<Module<'js>>
where
N: Into<Vec<u8>>,
D: ModuleDef,
{
let name = CString::new(name)?;
let mut defs = Declarations::new();
D::declare(&mut defs)?;
let ptr =
unsafe { qjs::JS_NewCModule(ctx.as_ptr(), name.as_ptr(), Some(Self::eval_fn::<D>)) };
let ptr = NonNull::new(ptr).ok_or(Error::Allocation)?;
let module = Module::from_module_def(ctx.clone(), ptr);
unsafe { defs.apply(ctx, &module)? };
Ok(module)
}
unsafe extern "C" fn eval_fn<D>(
ctx: *mut qjs::JSContext,
ptr: *mut qjs::JSModuleDef,
) -> qjs::c_int
where
D: ModuleDef,
{
let ctx = Ctx::from_ptr(ctx);
debug_assert_ne!(ptr, ptr::null_mut());
let ptr = NonNull::new_unchecked(ptr);
let module = Self::from_module_def(ctx.clone(), ptr);
let mut exports = Exports::new(ctx.clone());
match D::evaluate(&ctx, &mut exports).and_then(|_| exports.apply(module)) {
Ok(_) => 0,
Err(error) => {
error.throw(&ctx);
-1
}
}
}
pub unsafe fn eval(&self) -> Result<()> {
unsafe {
let value = qjs::JS_MKPTR(qjs::JS_TAG_MODULE, self.module.as_ptr().cast());
let ret = qjs::JS_EvalFunction(self.ctx.as_ptr(), qjs::JS_DupValue(value));
self.ctx.handle_exception(ret)?;
}
Ok(())
}
pub fn import<V: FromJs<'js>, S: AsRef<[u8]>>(ctx: &Ctx<'js>, specifier: S) -> Result<V> {
let specifier = specifier.as_ref();
let val = unsafe {
let js_string_val = qjs::JS_NewStringLen(
ctx.as_ptr(),
specifier.as_ptr() as *mut _,
specifier.len() as qjs::size_t,
);
let js_string = qjs::JS_ToCString(ctx.as_ptr(), js_string_val);
let val = qjs::JS_DynamicImportSync(ctx.as_ptr(), js_string);
qjs::JS_FreeValue(ctx.as_ptr(), js_string_val);
let val = ctx.handle_exception(val)?;
Value::from_js_value(ctx.clone(), val)
};
V::from_js(ctx, val)
}
}
#[cfg(feature = "exports")]
impl<'js> Module<'js> {
pub fn get<N, T>(&self, name: N) -> Result<T>
where
N: AsRef<str>,
T: FromJs<'js>,
{
let name = CString::new(name.as_ref())?;
let value = unsafe {
Value::from_js_value(
self.ctx.clone(),
self.ctx.handle_exception(qjs::JS_GetModuleExport(
self.ctx.as_ptr(),
self.as_module_def().as_ptr(),
name.as_ptr(),
))?,
)
};
T::from_js(&self.ctx, value)
}
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "exports")))]
pub fn names<N>(self) -> ExportNamesIter<'js, N>
where
N: FromAtom<'js>,
{
let count = unsafe { qjs::JS_GetModuleExportEntriesCount(self.as_module_def().as_ptr()) };
ExportNamesIter {
module: self,
count,
index: 0,
marker: PhantomData,
}
}
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "exports")))]
pub fn entries<N, T>(self) -> ExportEntriesIter<'js, N, T>
where
N: FromAtom<'js>,
T: FromJs<'js>,
{
let count = unsafe { qjs::JS_GetModuleExportEntriesCount(self.as_module_def().as_ptr()) };
ExportEntriesIter {
module: self,
count,
index: 0,
marker: PhantomData,
}
}
#[doc(hidden)]
pub unsafe fn dump_exports(&self) {
let ptr = self.as_module_def().as_ptr();
let count = qjs::JS_GetModuleExportEntriesCount(ptr);
for i in 0..count {
let atom_name = Atom::from_atom_val(
self.ctx.clone(),
qjs::JS_GetModuleExportEntryName(self.ctx.as_ptr(), ptr, i),
);
println!("{}", atom_name.to_string().unwrap());
}
}
}
#[cfg(feature = "exports")]
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "exports")))]
pub struct ExportNamesIter<'js, N> {
module: Module<'js>,
count: i32,
index: i32,
marker: PhantomData<N>,
}
#[cfg(feature = "exports")]
impl<'js, N> Iterator for ExportNamesIter<'js, N>
where
N: FromAtom<'js>,
{
type Item = Result<N>;
fn next(&mut self) -> Option<Self::Item> {
if self.index == self.count {
return None;
}
let ctx = &self.module.ctx;
let ptr = self.module.as_module_def().as_ptr();
let atom = unsafe {
let atom_val = qjs::JS_GetModuleExportEntryName(ctx.as_ptr(), ptr, self.index);
Atom::from_atom_val(ctx.clone(), atom_val)
};
self.index += 1;
Some(N::from_atom(atom))
}
}
#[cfg(feature = "exports")]
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "exports")))]
pub struct ExportEntriesIter<'js, N, T> {
module: Module<'js>,
count: i32,
index: i32,
marker: PhantomData<(N, T)>,
}
#[cfg(feature = "exports")]
impl<'js, N, T> Iterator for ExportEntriesIter<'js, N, T>
where
N: FromAtom<'js>,
T: FromJs<'js>,
{
type Item = Result<(N, T)>;
fn next(&mut self) -> Option<Self::Item> {
if self.index == self.count {
return None;
}
let ctx = &self.module.ctx;
let ptr = self.module.as_module_def().as_ptr();
let name = unsafe {
let atom_val = qjs::JS_GetModuleExportEntryName(ctx.as_ptr(), ptr, self.index);
Atom::from_atom_val(ctx.clone(), atom_val)
};
let value = unsafe {
let js_val = qjs::JS_GetModuleExportEntry(ctx.as_ptr(), ptr, self.index);
Value::from_js_value(ctx.clone(), js_val)
};
self.index += 1;
Some(N::from_atom(name).and_then(|name| T::from_js(ctx, value).map(|value| (name, value))))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::*;
pub struct RustModule;
impl ModuleDef for RustModule {
fn declare(define: &mut Declarations) -> Result<()> {
define.declare_static(CStr::from_bytes_with_nul(b"hello\0")?)?;
Ok(())
}
fn evaluate<'js>(_ctx: &Ctx<'js>, exports: &mut Exports<'js>) -> Result<()> {
exports.export("hello", "world".to_string())?;
Ok(())
}
}
pub struct CrashingRustModule;
impl ModuleDef for CrashingRustModule {
fn declare(_: &mut Declarations) -> Result<()> {
Ok(())
}
fn evaluate<'js>(ctx: &Ctx<'js>, _exports: &mut Exports<'js>) -> Result<()> {
ctx.eval::<(), _>(r#"throw new Error("kaboom")"#)?;
Ok(())
}
}
#[test]
fn from_rust_def() {
test_with(|ctx| {
Module::declare_def::<RustModule, _>(ctx, "rust_mod").unwrap();
})
}
#[test]
fn from_rust_def_eval() {
test_with(|ctx| {
let _ = Module::evaluate_def::<RustModule, _>(ctx, "rust_mod").unwrap();
})
}
#[test]
fn import_native() {
test_with(|ctx| {
Module::declare_def::<RustModule, _>(ctx.clone(), "rust_mod").unwrap();
let _ = ctx
.clone()
.compile(
"test",
r#"
import { hello } from "rust_mod";
globalThis.hello = hello;
"#,
)
.unwrap();
let text = ctx
.globals()
.get::<_, String>("hello")
.unwrap()
.to_string()
.unwrap();
assert_eq!(text.as_str(), "world");
})
}
#[test]
fn import() {
test_with(|ctx| {
Module::declare_def::<RustModule, _>(ctx.clone(), "rust_mod").unwrap();
let val: Object = Module::import(&ctx, "rust_mod").unwrap();
let hello: StdString = val.get("hello").unwrap();
assert_eq!(&hello, "world");
})
}
#[test]
#[should_panic(expected = "kaboom")]
fn import_crashing() {
use crate::{CatchResultExt, Context, Runtime};
let runtime = Runtime::new().unwrap();
let ctx = Context::full(&runtime).unwrap();
ctx.with(|ctx| {
Module::declare_def::<CrashingRustModule, _>(ctx.clone(), "bad_rust_mod").unwrap();
let _: Value = Module::import(&ctx, "bad_rust_mod").catch(&ctx).unwrap();
});
}
#[test]
fn holding_onto_unevaluated() {
let runtime = Runtime::new().unwrap();
let ctx = Context::full(&runtime).unwrap();
ctx.with(|ctx| {
let module = unsafe {
Module::unsafe_declare(
ctx.clone(),
"test",
"export function add(a,b){ return a + b }",
)
.unwrap()
};
ctx.compile("test2", "throw new Error(1)").ok();
unsafe { module.eval().unwrap() }
});
}
#[test]
fn eval_crashing_module_inside_module() {
let runtime = Runtime::new().unwrap();
let ctx = Context::full(&runtime).unwrap();
ctx.with(|ctx| {
let globals = ctx.globals();
let eval_crashing = |ctx: Ctx| ctx.compile("test2", "throw new Error(1)").map(|_| ());
let function = Function::new(ctx.clone(), eval_crashing).unwrap();
globals.set("eval_crashing", function).unwrap();
let res = ctx.compile("test", " eval_crashing(); ");
assert!(res.is_err())
});
}
#[test]
fn from_javascript() {
test_with(|ctx| {
let module: Module = ctx
.clone()
.compile(
"Test",
r#"
export var a = 2;
export function foo(){ return "bar"}
export class Baz{
quel = 3;
constructor(){
}
}
"#,
)
.unwrap();
assert_eq!(module.name::<StdString>().unwrap(), "Test");
let _ = module.meta::<Object>().unwrap();
#[cfg(feature = "exports")]
{
let names = module
.clone()
.names()
.collect::<Result<Vec<StdString>>>()
.unwrap();
assert_eq!(names[0], "a");
assert_eq!(names[1], "foo");
assert_eq!(names[2], "Baz");
let entries = module
.clone()
.entries()
.collect::<Result<Vec<(StdString, Value)>>>()
.unwrap();
assert_eq!(entries[0].0, "a");
assert_eq!(i32::from_js(&ctx, entries[0].1.clone()).unwrap(), 2);
assert_eq!(entries[1].0, "foo");
assert_eq!(entries[2].0, "Baz");
}
});
}
}