use crate::{
Context, JsArgs, JsData, JsResult, JsString, JsValue,
builtins::BuiltInObject,
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::{IgnoreEq, JsNativeError},
js_string,
object::{JsObject, internal_methods::get_prototype_from_constructor},
property::Attribute,
realm::Realm,
string::StaticJsStrings,
vm::shadow_stack::ShadowEntry,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
pub(crate) mod aggregate;
pub(crate) mod eval;
pub(crate) mod range;
pub(crate) mod reference;
pub(crate) mod syntax;
pub(crate) mod r#type;
pub(crate) mod uri;
#[cfg(test)]
mod tests;
pub(crate) use self::aggregate::AggregateError;
pub(crate) use self::eval::EvalError;
pub(crate) use self::range::RangeError;
pub(crate) use self::reference::ReferenceError;
pub(crate) use self::syntax::SyntaxError;
pub(crate) use self::r#type::TypeError;
pub(crate) use self::uri::UriError;
use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject};
#[derive(Debug, Copy, Clone, Eq, PartialEq, Trace, Finalize, JsData)]
#[boa_gc(empty_trace)]
#[non_exhaustive]
pub enum ErrorKind {
Aggregate,
Error,
Eval,
Type,
Range,
Reference,
Syntax,
Uri,
}
#[derive(Debug, Clone, PartialEq, Eq, Trace, Finalize, JsData)]
pub struct Error {
pub(crate) tag: ErrorKind,
#[unsafe_ignore_trace]
pub(crate) position: IgnoreEq<Option<ShadowEntry>>,
}
impl Error {
#[inline]
#[must_use]
pub fn new(tag: ErrorKind) -> Self {
Self {
tag,
position: IgnoreEq(None),
}
}
pub(crate) fn with_shadow_entry(tag: ErrorKind, entry: Option<ShadowEntry>) -> Self {
Self {
tag,
position: IgnoreEq(entry),
}
}
pub(crate) fn with_caller_position(tag: ErrorKind, context: &Context) -> Self {
Self {
tag,
position: IgnoreEq(context.vm.shadow_stack.caller_position()),
}
}
}
impl IntrinsicObject for Error {
fn init(realm: &Realm) {
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
let builder = BuiltInBuilder::from_standard_constructor::<Self>(realm)
.property(js_string!("name"), Self::NAME, attribute)
.property(js_string!("message"), js_string!(), attribute)
.method(Self::to_string, js_string!("toString"), 0);
#[cfg(feature = "experimental")]
let builder = builder.static_method(Error::is_error, js_string!("isError"), 1);
builder.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
}
}
impl BuiltInObject for Error {
const NAME: JsString = StaticJsStrings::ERROR;
}
impl BuiltInConstructor for Error {
const CONSTRUCTOR_ARGUMENTS: usize = 1;
const PROTOTYPE_STORAGE_SLOTS: usize = 3;
const CONSTRUCTOR_STORAGE_SLOTS: usize = 1;
const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
StandardConstructors::error;
fn constructor(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let new_target = &if new_target.is_undefined() {
context
.active_function_object()
.unwrap_or_else(|| context.intrinsics().constructors().error().constructor())
.into()
} else {
new_target.clone()
};
let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::error, context)?;
let o = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
Error::with_caller_position(ErrorKind::Error, context),
);
let message = args.get_or_undefined(0);
if !message.is_undefined() {
let msg = message.to_string(context)?;
o.create_non_enumerable_data_property_or_throw(js_string!("message"), msg, context);
}
Self::install_error_cause(&o, args.get_or_undefined(1), context)?;
Ok(o.into())
}
}
impl Error {
pub(crate) fn install_error_cause(
o: &JsObject,
options: &JsValue,
context: &mut Context,
) -> JsResult<()> {
if let Some(options) = options.as_object()
&& let Some(cause) = options.try_get(js_string!("cause"), context)?
{
o.create_non_enumerable_data_property_or_throw(js_string!("cause"), cause, context);
}
Ok(())
}
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let o = this
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("'this' is not an Object"))?;
let name = o.get(js_string!("name"), context)?;
let name = if name.is_undefined() {
js_string!("Error")
} else {
name.to_string(context)?
};
let msg = o.get(js_string!("message"), context)?;
let msg = if msg.is_undefined() {
js_string!()
} else {
msg.to_string(context)?
};
if name.is_empty() {
return Ok(msg.into());
}
if msg.is_empty() {
return Ok(name.into());
}
Ok(js_string!(&name, js_str!(": "), &msg).into())
}
#[cfg(feature = "experimental")]
#[allow(clippy::unnecessary_wraps)]
fn is_error(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Ok(args
.get_or_undefined(0)
.as_object()
.is_some_and(|o| o.is::<Error>())
.into())
}
}