1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
//! Boa's **`boa_engine`** crate implements ECMAScript's standard library of builtin objects
//! and an ECMAScript context, bytecompiler, and virtual machine for code execution.
//!
//! # Example usage
//!
//! You can find multiple examples of the usage of Boa in the [`boa_examples`][examples] crate. In
//! order to use Boa in your project, you will need to add the `boa_engine` crate to your
//! `Cargo.toml` file. You will need to use a [`Source`] structure to handle the JavaScript code
//! to execute, and a [`Context`] structure to execute the code:
//!
//! ```
//! use boa_engine::{Context, Source};
//!
//! let js_code = r#"
//! let two = 1 + 1;
//! let definitely_not_four = two + "2";
//!
//! definitely_not_four
//! "#;
//!
//! // Instantiate the execution context
//! let mut context = Context::default();
//!
//! // Parse the source code
//! match context.eval(Source::from_bytes(js_code)) {
//! Ok(res) => {
//! println!(
//! "{}",
//! res.to_string(&mut context).unwrap().to_std_string_escaped()
//! );
//! }
//! Err(e) => {
//! // Pretty print the error
//! eprintln!("Uncaught {e}");
//! # panic!("There was an error in boa_engine's introduction example.");
//! }
//! };
//! ```
//!
//! # Crate Features
//!
//! - **serde** - Enables serialization and deserialization of the AST (Abstract Syntax Tree).
//! - **profiler** - Enables profiling with measureme (this is mostly internal).
//! - **intl** - Enables `boa`'s [ECMA-402 Internationalization API][ecma-402] (`Intl` object)
//!
//! [ecma-402]: https://tc39.es/ecma402
//! [examples]: https://github.com/boa-dev/boa/tree/main/boa_examples
#![doc = include_str!("../ABOUT.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg"
)]
#![cfg_attr(test, allow(clippy::needless_raw_string_hashes))] // Makes strings a bit more copy-pastable
#![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,
)]
#[cfg(not(target_has_atomic = "ptr"))]
compile_error!("Boa requires a lock free `AtomicUsize` in order to work properly.");
extern crate self as boa_engine;
extern crate static_assertions as sa;
pub mod bigint;
pub mod builtins;
pub mod bytecompiler;
pub mod class;
pub mod context;
pub mod environments;
pub mod error;
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 small_map;
mod sys;
mod tagged;
#[cfg(test)]
mod tests;
/// A convenience module that re-exports the most commonly-used Boa APIs
pub mod prelude {
pub use crate::{
bigint::JsBigInt,
context::Context,
error::{JsError, JsNativeError, JsNativeErrorKind},
host_defined::HostDefined,
module::Module,
native_function::NativeFunction,
object::{JsData, JsObject, NativeObject},
script::Script,
string::JsString,
symbol::JsSymbol,
value::JsValue,
};
pub use boa_gc::{Finalize, Trace};
pub use boa_macros::JsData;
pub use boa_parser::Source;
}
use std::result::Result as StdResult;
// Export things to root level
#[doc(inline)]
pub use prelude::*;
/// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`)
pub type JsResult<T> = StdResult<T, JsError>;
/// A utility trait to make working with function arguments easier.
pub trait JsArgs {
/// Utility function to `get` a parameter from a `[JsValue]` or default to `JsValue::Undefined`
/// if `get` returns `None`.
///
/// Call this if you are thinking of calling something similar to
/// `args.get(n).cloned().unwrap_or_default()` or
/// `args.get(n).unwrap_or(&undefined)`.
///
/// This returns a reference for efficiency, in case you only need to call methods of `JsValue`.
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;
/// A test action executed in a test function.
#[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 {
/// Evaluates some utility functions used in tests.
const fn run_harness() -> Self {
Self(Inner::RunHarness)
}
/// Runs `source`, panicking if the execution throws.
fn run(source: impl Into<Cow<'static, str>>) -> Self {
Self(Inner::Run {
source: source.into(),
})
}
/// Executes `op` with the currently active context.
///
/// Useful to make custom assertions that must be done from Rust code.
fn inspect_context(op: fn(&mut Context)) -> Self {
Self(Inner::InspectContext { op })
}
/// Asserts that evaluating `source` returns the `true` value.
fn assert(source: impl Into<Cow<'static, str>>) -> Self {
Self(Inner::Assert {
source: source.into(),
})
}
/// Asserts that the script returns `expected` when evaluating `source`.
fn assert_eq(source: impl Into<Cow<'static, str>>, expected: impl Into<JsValue>) -> Self {
Self(Inner::AssertEq {
source: source.into(),
expected: expected.into(),
})
}
/// Asserts that calling `op` with the value obtained from evaluating `source` returns `true`.
///
/// Useful to check properties of the obtained value that cannot be checked from JS code.
fn assert_with_op(
source: impl Into<Cow<'static, str>>,
op: fn(JsValue, &mut Context) -> bool,
) -> Self {
Self(Inner::AssertWithOp {
source: source.into(),
op,
})
}
/// Asserts that evaluating `source` throws the opaque error `value`.
fn assert_opaque_error(
source: impl Into<Cow<'static, str>>,
value: impl Into<JsValue>,
) -> Self {
Self(Inner::AssertOpaqueError {
source: source.into(),
expected: value.into(),
})
}
/// Asserts that evaluating `source` throws a native error of `kind` and `message`.
fn assert_native_error(
source: impl Into<Cow<'static, str>>,
kind: JsNativeErrorKind,
message: &'static str,
) -> Self {
Self(Inner::AssertNativeError {
source: source.into(),
kind,
message,
})
}
/// Asserts that calling `op` with the currently executing context returns `true`.
fn assert_context(op: fn(&mut Context) -> bool) -> Self {
Self(Inner::AssertContext { op })
}
}
/// Executes a list of test actions on a new, default context.
#[cfg(test)]
#[track_caller]
fn run_test_actions(actions: impl IntoIterator<Item = TestAction>) {
let context = &mut Context::default();
run_test_actions_with(actions, context);
}
/// Executes a list of test actions on the provided context.
#[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, " ")
)
}
// Some unwrapping patterns look weird because they're replaceable
// by simpler patterns like `unwrap_or_else` or `unwrap_err
let mut i = 1;
for action in actions.into_iter().map(|a| a.0) {
match action {
Inner::RunHarness => {
// add utility functions for testing
// TODO: extract to a file
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;
}
}
}
}