#![doc = include_str!("../ABOUT.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo_black.svg",
html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo_black.svg"
)]
#![cfg_attr(test, allow(clippy::needless_raw_string_hashes))] #![cfg_attr(not(test), forbid(clippy::unwrap_used))]
#![allow(
// Currently throws a false positive regarding dependencies that are only used in benchmarks.
unused_crate_dependencies,
clippy::module_name_repetitions,
clippy::redundant_pub_crate,
clippy::too_many_lines,
clippy::cognitive_complexity,
clippy::missing_errors_doc,
clippy::let_unit_value,
clippy::option_if_let_else,
// It may be worth to look if we can fix the issues highlighted by these lints.
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::cast_possible_wrap,
// Add temporarily - Needs addressing
clippy::missing_panics_doc,
)]
extern crate self as boa_engine;
#[cfg(not(target_has_atomic = "ptr"))]
compile_error!("Boa requires a lock free `AtomicUsize` in order to work properly.");
pub use boa_ast as ast;
pub use boa_gc as gc;
pub use boa_interner as interner;
pub use boa_parser as parser;
pub mod bigint;
pub mod builtins;
pub mod bytecompiler;
pub mod class;
pub mod context;
pub mod environments;
pub mod error;
pub mod interop;
pub mod job;
pub mod module;
pub mod native_function;
pub mod object;
pub mod optimizer;
pub mod property;
pub mod realm;
pub mod script;
pub mod string;
pub mod symbol;
pub mod value;
pub mod vm;
mod host_defined;
mod sys;
mod spanned_source_text;
use spanned_source_text::SourceText;
pub use spanned_source_text::SpannedSourceText;
#[cfg(test)]
mod tests;
pub mod prelude {
pub use crate::{
bigint::JsBigInt,
context::Context,
error::{JsError, JsNativeError, JsNativeErrorKind},
host_defined::HostDefined,
interop::{IntoJsFunctionCopied, UnsafeIntoJsFunction},
module::{IntoJsModule, Module},
native_function::NativeFunction,
object::{JsData, JsObject, NativeObject},
script::Script,
string::{JsStr, JsString},
symbol::JsSymbol,
value::{JsValue, JsVariant, js_object, js_value},
};
pub use boa_gc::{Finalize, Trace};
pub use boa_macros::{JsData, js_str};
pub use boa_parser::Source;
}
#[doc(inline)]
pub use boa_macros::{boa_class, boa_module, embed_module_inner as __embed_module_inner};
use std::result::Result as StdResult;
#[doc(inline)]
pub use prelude::*;
#[doc(inline)]
pub use boa_parser::Source;
pub type JsResult<T> = StdResult<T, JsError>;
pub trait TryIntoJsResult {
fn try_into_js_result(self, context: &mut Context) -> JsResult<JsValue>;
}
mod try_into_js_result_impls;
pub trait JsArgs {
fn get_or_undefined(&self, index: usize) -> &JsValue;
}
impl JsArgs for [JsValue] {
fn get_or_undefined(&self, index: usize) -> &JsValue {
const UNDEFINED: &JsValue = &JsValue::undefined();
self.get(index).unwrap_or(UNDEFINED)
}
}
#[cfg(test)]
use std::borrow::Cow;
#[cfg(test)]
#[derive(Clone)]
struct TestAction(Inner);
#[cfg(test)]
#[derive(Clone)]
enum Inner {
RunHarness,
Run {
source: Cow<'static, str>,
},
InspectContext {
op: fn(&mut Context),
},
Assert {
source: Cow<'static, str>,
},
AssertEq {
source: Cow<'static, str>,
expected: JsValue,
},
AssertWithOp {
source: Cow<'static, str>,
op: fn(JsValue, &mut Context) -> bool,
},
AssertOpaqueError {
source: Cow<'static, str>,
expected: JsValue,
},
AssertNativeError {
source: Cow<'static, str>,
kind: JsNativeErrorKind,
message: &'static str,
},
AssertContext {
op: fn(&mut Context) -> bool,
},
}
#[cfg(test)]
impl TestAction {
const fn run_harness() -> Self {
Self(Inner::RunHarness)
}
fn run(source: impl Into<Cow<'static, str>>) -> Self {
Self(Inner::Run {
source: source.into(),
})
}
fn inspect_context(op: fn(&mut Context)) -> Self {
Self(Inner::InspectContext { op })
}
fn assert(source: impl Into<Cow<'static, str>>) -> Self {
Self(Inner::Assert {
source: source.into(),
})
}
fn assert_eq(source: impl Into<Cow<'static, str>>, expected: impl Into<JsValue>) -> Self {
Self(Inner::AssertEq {
source: source.into(),
expected: expected.into(),
})
}
fn assert_with_op(
source: impl Into<Cow<'static, str>>,
op: fn(JsValue, &mut Context) -> bool,
) -> Self {
Self(Inner::AssertWithOp {
source: source.into(),
op,
})
}
fn assert_opaque_error(
source: impl Into<Cow<'static, str>>,
value: impl Into<JsValue>,
) -> Self {
Self(Inner::AssertOpaqueError {
source: source.into(),
expected: value.into(),
})
}
fn assert_native_error(
source: impl Into<Cow<'static, str>>,
kind: JsNativeErrorKind,
message: &'static str,
) -> Self {
Self(Inner::AssertNativeError {
source: source.into(),
kind,
message,
})
}
fn assert_context(op: fn(&mut Context) -> bool) -> Self {
Self(Inner::AssertContext { op })
}
}
#[cfg(test)]
#[track_caller]
fn run_test_actions(actions: impl IntoIterator<Item = TestAction>) {
let mut context = Context::builder();
if cfg!(miri) {
use std::rc::Rc;
use crate::{context::time::FixedClock, module::IdleModuleLoader};
context = context
.clock(Rc::new(FixedClock::from_millis(65535)))
.module_loader(Rc::new(IdleModuleLoader));
}
run_test_actions_with(actions, &mut context.build().unwrap());
}
#[cfg(test)]
#[track_caller]
fn run_test_actions_with(actions: impl IntoIterator<Item = TestAction>, context: &mut Context) {
#[track_caller]
fn forward_val(context: &mut Context, source: &str) -> JsResult<JsValue> {
context.eval(Source::from_bytes(source))
}
#[track_caller]
fn fmt_test(source: &str, test: usize) -> String {
format!(
"\n\nTest case {test}: \n```\n{}\n```",
textwrap::indent(source, " ")
)
}
let mut i = 1;
for action in actions.into_iter().map(|a| a.0) {
match action {
Inner::RunHarness => {
forward_val(
context,
r#"
function equals(a, b) {
if (Array.isArray(a) && Array.isArray(b)) {
return arrayEquals(a, b);
}
return a === b;
}
function arrayEquals(a, b) {
return Array.isArray(a) &&
Array.isArray(b) &&
a.length === b.length &&
a.every((val, index) => equals(val, b[index]));
}
"#,
)
.expect("failed to evaluate test harness");
}
Inner::Run { source } => {
if let Err(e) = forward_val(context, &source) {
panic!("{}\nUncaught {e}", fmt_test(&source, i));
}
}
Inner::InspectContext { op } => {
op(context);
}
Inner::Assert { source } => {
let val = match forward_val(context, &source) {
Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)),
Ok(v) => v,
};
let Some(val) = val.as_boolean() else {
panic!(
"{}\nTried to assert with the non-boolean value `{}`",
fmt_test(&source, i),
val.display()
)
};
assert!(val, "{}", fmt_test(&source, i));
i += 1;
}
Inner::AssertEq { source, expected } => {
let val = match forward_val(context, &source) {
Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)),
Ok(v) => v,
};
assert_eq!(val, expected, "{}", fmt_test(&source, i));
i += 1;
}
Inner::AssertWithOp { source, op } => {
let val = match forward_val(context, &source) {
Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)),
Ok(v) => v,
};
assert!(op(val, context), "{}", fmt_test(&source, i));
i += 1;
}
Inner::AssertOpaqueError { source, expected } => {
let err = match forward_val(context, &source) {
Ok(v) => panic!(
"{}\nExpected error, got value `{}`",
fmt_test(&source, i),
v.display()
),
Err(e) => e,
};
let Some(err) = err.as_opaque() else {
panic!(
"{}\nExpected opaque error, got native error `{}`",
fmt_test(&source, i),
err
)
};
assert_eq!(err, &expected, "{}", fmt_test(&source, i));
i += 1;
}
Inner::AssertNativeError {
source,
kind,
message,
} => {
let err = match forward_val(context, &source) {
Ok(v) => panic!(
"{}\nExpected error, got value `{}`",
fmt_test(&source, i),
v.display()
),
Err(e) => e,
};
let native = match err.try_native(context) {
Ok(err) => err,
Err(e) => panic!(
"{}\nCouldn't obtain a native error: {e}",
fmt_test(&source, i)
),
};
assert_eq!(&native.kind, &kind, "{}", fmt_test(&source, i));
assert_eq!(native.message(), message, "{}", fmt_test(&source, i));
i += 1;
}
Inner::AssertContext { op } => {
assert!(op(context), "Test case {i}");
i += 1;
}
}
}
}