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;
98pub mod property;
99pub mod realm;
100pub mod script;
101pub mod string;
102pub mod symbol;
103pub mod value;
104pub mod vm;
105
106mod host_defined;
107mod sys;
108
109mod spanned_source_text;
110use spanned_source_text::SourceText;
111pub use spanned_source_text::SpannedSourceText;
112
113#[cfg(test)]
114mod tests;
115
116pub mod prelude {
118 pub use crate::{
119 bigint::JsBigInt,
120 context::Context,
121 error::{JsError, JsNativeError, JsNativeErrorKind},
122 host_defined::HostDefined,
123 interop::{IntoJsFunctionCopied, UnsafeIntoJsFunction},
124 module::{IntoJsModule, Module},
125 native_function::NativeFunction,
126 object::{JsData, JsObject, NativeObject},
127 script::Script,
128 string::{JsStr, JsString},
129 symbol::JsSymbol,
130 value::{JsValue, JsVariant, js_object, js_value},
131 };
132 pub use boa_gc::{Finalize, Trace};
133 pub use boa_macros::{JsData, js_str};
134 pub use boa_parser::Source;
135}
136
137#[doc(inline)]
138pub use boa_macros::{boa_class, boa_module, embed_module_inner as __embed_module_inner};
139
140use std::result::Result as StdResult;
141
142#[doc(inline)]
144pub use prelude::*;
145
146#[doc(inline)]
147pub use boa_parser::Source;
148
149pub type JsResult<T> = StdResult<T, JsError>;
151
152pub trait TryIntoJsResult {
158 fn try_into_js_result(self, context: &mut Context) -> JsResult<JsValue>;
164}
165
166mod try_into_js_result_impls;
167
168pub trait JsArgs {
170 fn get_or_undefined(&self, index: usize) -> &JsValue;
179}
180
181impl JsArgs for [JsValue] {
182 fn get_or_undefined(&self, index: usize) -> &JsValue {
183 const UNDEFINED: &JsValue = &JsValue::undefined();
184 self.get(index).unwrap_or(UNDEFINED)
185 }
186}
187
188#[cfg(test)]
189use std::borrow::Cow;
190
191#[cfg(test)]
193#[derive(Clone)]
194struct TestAction(Inner);
195
196#[cfg(test)]
197#[derive(Clone)]
198enum Inner {
199 RunHarness,
200 Run {
201 source: Cow<'static, str>,
202 },
203 InspectContext {
204 op: fn(&mut Context),
205 },
206 Assert {
207 source: Cow<'static, str>,
208 },
209 AssertEq {
210 source: Cow<'static, str>,
211 expected: JsValue,
212 },
213 AssertWithOp {
214 source: Cow<'static, str>,
215 op: fn(JsValue, &mut Context) -> bool,
216 },
217 AssertOpaqueError {
218 source: Cow<'static, str>,
219 expected: JsValue,
220 },
221 AssertNativeError {
222 source: Cow<'static, str>,
223 kind: JsNativeErrorKind,
224 message: &'static str,
225 },
226 AssertContext {
227 op: fn(&mut Context) -> bool,
228 },
229}
230
231#[cfg(test)]
232impl TestAction {
233 const fn run_harness() -> Self {
235 Self(Inner::RunHarness)
236 }
237
238 fn run(source: impl Into<Cow<'static, str>>) -> Self {
240 Self(Inner::Run {
241 source: source.into(),
242 })
243 }
244
245 fn inspect_context(op: fn(&mut Context)) -> Self {
249 Self(Inner::InspectContext { op })
250 }
251
252 fn assert(source: impl Into<Cow<'static, str>>) -> Self {
254 Self(Inner::Assert {
255 source: source.into(),
256 })
257 }
258
259 fn assert_eq(source: impl Into<Cow<'static, str>>, expected: impl Into<JsValue>) -> Self {
261 Self(Inner::AssertEq {
262 source: source.into(),
263 expected: expected.into(),
264 })
265 }
266
267 fn assert_with_op(
271 source: impl Into<Cow<'static, str>>,
272 op: fn(JsValue, &mut Context) -> bool,
273 ) -> Self {
274 Self(Inner::AssertWithOp {
275 source: source.into(),
276 op,
277 })
278 }
279
280 fn assert_opaque_error(
282 source: impl Into<Cow<'static, str>>,
283 value: impl Into<JsValue>,
284 ) -> Self {
285 Self(Inner::AssertOpaqueError {
286 source: source.into(),
287 expected: value.into(),
288 })
289 }
290
291 fn assert_native_error(
293 source: impl Into<Cow<'static, str>>,
294 kind: JsNativeErrorKind,
295 message: &'static str,
296 ) -> Self {
297 Self(Inner::AssertNativeError {
298 source: source.into(),
299 kind,
300 message,
301 })
302 }
303
304 fn assert_context(op: fn(&mut Context) -> bool) -> Self {
306 Self(Inner::AssertContext { op })
307 }
308}
309
310#[cfg(test)]
312#[track_caller]
313fn run_test_actions(actions: impl IntoIterator<Item = TestAction>) {
314 let mut context = Context::builder();
315 if cfg!(miri) {
316 use std::rc::Rc;
320
321 use crate::{context::time::FixedClock, module::IdleModuleLoader};
322
323 context = context
324 .clock(Rc::new(FixedClock::from_millis(65535)))
325 .module_loader(Rc::new(IdleModuleLoader));
326 }
327 run_test_actions_with(actions, &mut context.build().unwrap());
328}
329
330#[cfg(test)]
332#[track_caller]
333fn run_test_actions_with(actions: impl IntoIterator<Item = TestAction>, context: &mut Context) {
334 #[track_caller]
335 fn forward_val(context: &mut Context, source: &str) -> JsResult<JsValue> {
336 context.eval(Source::from_bytes(source))
337 }
338
339 #[track_caller]
340 fn fmt_test(source: &str, test: usize) -> String {
341 format!(
342 "\n\nTest case {test}: \n```\n{}\n```",
343 textwrap::indent(source, " ")
344 )
345 }
346
347 let mut i = 1;
350 for action in actions.into_iter().map(|a| a.0) {
351 match action {
352 Inner::RunHarness => {
353 forward_val(
356 context,
357 r#"
358 function equals(a, b) {
359 if (Array.isArray(a) && Array.isArray(b)) {
360 return arrayEquals(a, b);
361 }
362 return a === b;
363 }
364 function arrayEquals(a, b) {
365 return Array.isArray(a) &&
366 Array.isArray(b) &&
367 a.length === b.length &&
368 a.every((val, index) => equals(val, b[index]));
369 }
370 "#,
371 )
372 .expect("failed to evaluate test harness");
373 }
374 Inner::Run { source } => {
375 if let Err(e) = forward_val(context, &source) {
376 panic!("{}\nUncaught {e}", fmt_test(&source, i));
377 }
378 }
379 Inner::InspectContext { op } => {
380 op(context);
381 }
382 Inner::Assert { source } => {
383 let val = match forward_val(context, &source) {
384 Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)),
385 Ok(v) => v,
386 };
387 let Some(val) = val.as_boolean() else {
388 panic!(
389 "{}\nTried to assert with the non-boolean value `{}`",
390 fmt_test(&source, i),
391 val.display()
392 )
393 };
394 assert!(val, "{}", fmt_test(&source, i));
395 i += 1;
396 }
397 Inner::AssertEq { source, expected } => {
398 let val = match forward_val(context, &source) {
399 Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)),
400 Ok(v) => v,
401 };
402 assert_eq!(val, expected, "{}", fmt_test(&source, i));
403 i += 1;
404 }
405 Inner::AssertWithOp { source, op } => {
406 let val = match forward_val(context, &source) {
407 Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)),
408 Ok(v) => v,
409 };
410 assert!(op(val, context), "{}", fmt_test(&source, i));
411 i += 1;
412 }
413 Inner::AssertOpaqueError { source, expected } => {
414 let err = match forward_val(context, &source) {
415 Ok(v) => panic!(
416 "{}\nExpected error, got value `{}`",
417 fmt_test(&source, i),
418 v.display()
419 ),
420 Err(e) => e,
421 };
422 let Some(err) = err.as_opaque() else {
423 panic!(
424 "{}\nExpected opaque error, got native error `{}`",
425 fmt_test(&source, i),
426 err
427 )
428 };
429
430 assert_eq!(err, &expected, "{}", fmt_test(&source, i));
431 i += 1;
432 }
433 Inner::AssertNativeError {
434 source,
435 kind,
436 message,
437 } => {
438 let err = match forward_val(context, &source) {
439 Ok(v) => panic!(
440 "{}\nExpected error, got value `{}`",
441 fmt_test(&source, i),
442 v.display()
443 ),
444 Err(e) => e,
445 };
446 let native = match err.try_native(context) {
447 Ok(err) => err,
448 Err(e) => panic!(
449 "{}\nCouldn't obtain a native error: {e}",
450 fmt_test(&source, i)
451 ),
452 };
453
454 assert_eq!(&native.kind, &kind, "{}", fmt_test(&source, i));
455 assert_eq!(native.message(), message, "{}", fmt_test(&source, i));
456 i += 1;
457 }
458 Inner::AssertContext { op } => {
459 assert!(op(context), "Test case {i}");
460 i += 1;
461 }
462 }
463 }
464}