1#![doc = include_str!("../ABOUT.md")]
49#![doc(
50 html_logo_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo_black.svg",
51 html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo_black.svg"
52)]
53#![cfg_attr(test, allow(clippy::needless_raw_string_hashes))] #![cfg_attr(not(test), forbid(clippy::unwrap_used))]
55#![allow(
56 unused_crate_dependencies,
58 clippy::module_name_repetitions,
59 clippy::redundant_pub_crate,
60 clippy::too_many_lines,
61 clippy::cognitive_complexity,
62 clippy::missing_errors_doc,
63 clippy::let_unit_value,
64 clippy::option_if_let_else,
65
66 clippy::cast_possible_truncation,
68 clippy::cast_sign_loss,
69 clippy::cast_precision_loss,
70 clippy::cast_possible_wrap,
71
72 clippy::missing_panics_doc,
74)]
75
76extern crate self as boa_engine;
77#[cfg(not(target_has_atomic = "ptr"))]
78compile_error!("Boa requires a lock free `AtomicUsize` in order to work properly.");
79
80pub use boa_ast as ast;
81pub use boa_gc as gc;
82pub use boa_interner as interner;
83pub use boa_parser as parser;
84
85pub mod bigint;
86pub mod builtins;
87pub mod bytecompiler;
88pub mod class;
89pub mod context;
90pub mod environments;
91pub mod error;
92pub mod interop;
93pub mod job;
94pub mod module;
95pub mod native_function;
96pub mod object;
97pub mod optimizer;
99pub mod property;
100pub mod realm;
101pub mod script;
102pub mod string;
103pub mod symbol;
104pub mod value;
105pub mod vm;
106
107mod host_defined;
108mod sys;
109
110mod spanned_source_text;
111use spanned_source_text::SourceText;
112pub use spanned_source_text::SpannedSourceText;
113
114#[cfg(test)]
115mod tests;
116
117pub mod prelude {
119 pub use crate::{
120 bigint::JsBigInt,
121 context::Context,
122 error::{EngineError, JsError, JsNativeError, JsNativeErrorKind, RuntimeLimitError},
123 host_defined::HostDefined,
124 interop::{IntoJsFunctionCopied, UnsafeIntoJsFunction},
125 module::{IntoJsModule, Module},
126 native_function::NativeFunction,
127 object::{JsData, JsObject, NativeObject},
128 script::Script,
129 string::{JsStr, JsString},
130 symbol::JsSymbol,
131 value::{JsValue, JsVariant, js_object, js_value},
132 };
133 pub use boa_gc::{Finalize, Trace};
134 pub use boa_macros::{JsData, js_str};
135 pub use boa_parser::Source;
136}
137
138#[doc(inline)]
139pub use boa_macros::{boa_class, boa_module, embed_module_inner as __embed_module_inner};
140
141use crate::error::PanicError;
142use std::result::Result as StdResult;
143
144#[doc(inline)]
146pub use prelude::*;
147
148#[doc(inline)]
149pub use boa_parser::Source;
150
151pub type JsResult<T> = StdResult<T, JsError>;
153
154pub trait TryIntoJsResult {
160 fn try_into_js_result(self, context: &mut Context) -> JsResult<JsValue>;
166}
167
168mod try_into_js_result_impls;
169
170pub trait JsArgs {
172 fn get_or_undefined(&self, index: usize) -> &JsValue;
181}
182
183impl JsArgs for [JsValue] {
184 fn get_or_undefined(&self, index: usize) -> &JsValue {
185 const UNDEFINED: &JsValue = &JsValue::undefined();
186 self.get(index).unwrap_or(UNDEFINED)
187 }
188}
189
190#[allow(dead_code)]
192pub(crate) trait JsExpect<V> {
193 fn js_expect<S: Into<Box<str>>>(self, msg: S) -> StdResult<V, PanicError>;
195}
196
197impl<V> JsExpect<V> for JsResult<V> {
198 fn js_expect<S: Into<Box<str>>>(self, msg: S) -> StdResult<V, PanicError> {
199 self.map_err(|err| PanicError::new(msg).with_source(err))
200 }
201}
202
203impl<V> JsExpect<V> for Option<V> {
204 fn js_expect<S: Into<Box<str>>>(self, msg: S) -> StdResult<V, PanicError> {
205 self.ok_or_else(|| PanicError::new(msg))
206 }
207}
208
209#[cfg(test)]
210use std::{borrow::Cow, pin::Pin};
211
212#[cfg(test)]
213type PinBoxFuture<'a> = Pin<Box<dyn Future<Output = ()> + 'a>>;
214
215#[cfg(test)]
217struct TestAction(Inner);
218
219#[cfg(test)]
220enum Inner {
221 RunHarness,
222 Run {
223 source: Cow<'static, str>,
224 },
225 InspectContext {
226 op: fn(&mut Context),
227 },
228 InspectContextAsync {
229 op: Box<dyn for<'a> FnOnce(&'a mut Context) -> PinBoxFuture<'a>>,
230 },
231 Assert {
232 source: Cow<'static, str>,
233 },
234 AssertEq {
235 source: Cow<'static, str>,
236 expected: JsValue,
237 },
238 AssertWithOp {
239 source: Cow<'static, str>,
240 op: fn(JsValue, &mut Context) -> bool,
241 },
242 AssertOpaqueError {
243 source: Cow<'static, str>,
244 expected: JsValue,
245 },
246 AssertNativeError {
247 source: Cow<'static, str>,
248 kind: JsNativeErrorKind,
249 message: &'static str,
250 },
251 AssertContext {
252 op: fn(&mut Context) -> bool,
253 },
254 AssertEngineError {
255 source: Cow<'static, str>,
256 error: EngineError,
257 },
258}
259
260#[cfg(test)]
261impl TestAction {
262 const fn run_harness() -> Self {
264 Self(Inner::RunHarness)
265 }
266
267 fn run(source: impl Into<Cow<'static, str>>) -> Self {
269 Self(Inner::Run {
270 source: source.into(),
271 })
272 }
273
274 fn inspect_context(op: fn(&mut Context)) -> Self {
278 Self(Inner::InspectContext { op })
279 }
280
281 pub(crate) fn inspect_context_async(op: impl AsyncFnOnce(&mut Context) + 'static) -> Self {
283 Self(Inner::InspectContextAsync {
284 op: Box::new(move |ctx| Box::pin(op(ctx))),
285 })
286 }
287
288 fn assert(source: impl Into<Cow<'static, str>>) -> Self {
290 Self(Inner::Assert {
291 source: source.into(),
292 })
293 }
294
295 fn assert_eq(source: impl Into<Cow<'static, str>>, expected: impl Into<JsValue>) -> Self {
297 Self(Inner::AssertEq {
298 source: source.into(),
299 expected: expected.into(),
300 })
301 }
302
303 fn assert_with_op(
307 source: impl Into<Cow<'static, str>>,
308 op: fn(JsValue, &mut Context) -> bool,
309 ) -> Self {
310 Self(Inner::AssertWithOp {
311 source: source.into(),
312 op,
313 })
314 }
315
316 fn assert_opaque_error(
318 source: impl Into<Cow<'static, str>>,
319 value: impl Into<JsValue>,
320 ) -> Self {
321 Self(Inner::AssertOpaqueError {
322 source: source.into(),
323 expected: value.into(),
324 })
325 }
326
327 fn assert_native_error(
329 source: impl Into<Cow<'static, str>>,
330 kind: JsNativeErrorKind,
331 message: &'static str,
332 ) -> Self {
333 Self(Inner::AssertNativeError {
334 source: source.into(),
335 kind,
336 message,
337 })
338 }
339
340 fn assert_runtime_limit_error(
342 source: impl Into<Cow<'static, str>>,
343 error: RuntimeLimitError,
344 ) -> Self {
345 Self(Inner::AssertEngineError {
346 source: source.into(),
347 error: error.into(),
348 })
349 }
350
351 fn assert_context(op: fn(&mut Context) -> bool) -> Self {
353 Self(Inner::AssertContext { op })
354 }
355}
356
357#[cfg(test)]
359#[track_caller]
360fn run_test_actions(actions: impl IntoIterator<Item = TestAction>) {
361 let mut context = Context::builder();
362 if cfg!(miri) {
363 use std::rc::Rc;
367
368 use crate::{context::time::FixedClock, module::IdleModuleLoader};
369
370 context = context
371 .clock(Rc::new(FixedClock::from_millis(65535)))
372 .module_loader(Rc::new(IdleModuleLoader));
373 }
374 run_test_actions_with(actions, &mut context.build().unwrap());
375}
376
377#[cfg(test)]
379#[track_caller]
380fn run_test_actions_with(actions: impl IntoIterator<Item = TestAction>, context: &mut Context) {
381 #[track_caller]
382 fn forward_val(context: &mut Context, source: &str) -> JsResult<JsValue> {
383 context.eval(Source::from_bytes(source))
384 }
385
386 #[track_caller]
387 fn fmt_test(source: &str, test: usize) -> String {
388 format!(
389 "\n\nTest case {test}: \n```\n{}\n```",
390 textwrap::indent(source, " ")
391 )
392 }
393
394 let mut i = 1;
397 for action in actions.into_iter().map(|a| a.0) {
398 match action {
399 Inner::RunHarness => {
400 forward_val(
403 context,
404 r#"
405 function equals(a, b) {
406 if (Array.isArray(a) && Array.isArray(b)) {
407 return arrayEquals(a, b);
408 }
409 return a === b;
410 }
411 function arrayEquals(a, b) {
412 return Array.isArray(a) &&
413 Array.isArray(b) &&
414 a.length === b.length &&
415 a.every((val, index) => equals(val, b[index]));
416 }
417 "#,
418 )
419 .expect("failed to evaluate test harness");
420 }
421 Inner::Run { source } => {
422 if let Err(e) = forward_val(context, &source) {
423 panic!("{}\nUncaught {e}", fmt_test(&source, i));
424 }
425 }
426 Inner::InspectContext { op } => {
427 op(context);
428 }
429 Inner::InspectContextAsync { op } => futures_lite::future::block_on(op(context)),
430 Inner::Assert { source } => {
431 let val = match forward_val(context, &source) {
432 Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)),
433 Ok(v) => v,
434 };
435 let Some(val) = val.as_boolean() else {
436 panic!(
437 "{}\nTried to assert with the non-boolean value `{}`",
438 fmt_test(&source, i),
439 val.display()
440 )
441 };
442 assert!(val, "{}", fmt_test(&source, i));
443 i += 1;
444 }
445 Inner::AssertEq { source, expected } => {
446 let val = match forward_val(context, &source) {
447 Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)),
448 Ok(v) => v,
449 };
450 assert_eq!(val, expected, "{}", fmt_test(&source, i));
451 i += 1;
452 }
453 Inner::AssertWithOp { source, op } => {
454 let val = match forward_val(context, &source) {
455 Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)),
456 Ok(v) => v,
457 };
458 assert!(op(val, context), "{}", fmt_test(&source, i));
459 i += 1;
460 }
461 Inner::AssertOpaqueError { source, expected } => {
462 let err = match forward_val(context, &source) {
463 Ok(v) => panic!(
464 "{}\nExpected error, got value `{}`",
465 fmt_test(&source, i),
466 v.display()
467 ),
468 Err(e) => e,
469 };
470 let Some(err) = err.as_opaque() else {
471 panic!(
472 "{}\nExpected opaque error, got native error `{}`",
473 fmt_test(&source, i),
474 err
475 )
476 };
477
478 assert_eq!(err, &expected, "{}", fmt_test(&source, i));
479 i += 1;
480 }
481 Inner::AssertNativeError {
482 source,
483 kind,
484 message,
485 } => {
486 let err = match forward_val(context, &source) {
487 Ok(v) => panic!(
488 "{}\nExpected error, got value `{}`",
489 fmt_test(&source, i),
490 v.display()
491 ),
492 Err(e) => e,
493 };
494 let native = match err.try_native(context) {
495 Ok(err) => err,
496 Err(e) => panic!(
497 "{}\nCouldn't obtain a native error: {e}",
498 fmt_test(&source, i)
499 ),
500 };
501
502 assert_eq!(native.kind(), &kind, "{}", fmt_test(&source, i));
503 assert_eq!(native.message(), message, "{}", fmt_test(&source, i));
504 i += 1;
505 }
506 Inner::AssertEngineError { source, error } => {
507 let err = match forward_val(context, &source) {
508 Ok(v) => panic!(
509 "{}\nExpected error, got value `{}`",
510 fmt_test(&source, i),
511 v.display()
512 ),
513 Err(e) => e,
514 };
515
516 let Some(err) = err.as_engine() else {
517 panic!(
518 "{}\nExpected a native error, got {err}",
519 fmt_test(&source, i)
520 )
521 };
522
523 assert_eq!(*err, error);
524 i += 1;
525 }
526 Inner::AssertContext { op } => {
527 assert!(op(context), "Test case {i}");
528 i += 1;
529 }
530 }
531 }
532}