diff --git a/boa/src/builtins/error/mod.rs b/boa/src/builtins/error/mod.rs index dd4d9d63a4..f19f0fcf67 100644 --- a/boa/src/builtins/error/mod.rs +++ b/boa/src/builtins/error/mod.rs @@ -20,16 +20,19 @@ use crate::{ profiler::BoaProfiler, }; -// mod eval; pub(crate) mod range; pub(crate) mod reference; -// mod syntax; +pub(crate) mod syntax; pub(crate) mod r#type; -// mod uri; +// pub(crate) mod eval; +// pub(crate) mod uri; pub(crate) use self::r#type::TypeError; pub(crate) use self::range::RangeError; pub(crate) use self::reference::ReferenceError; +pub(crate) use self::syntax::SyntaxError; +// pub(crate) use self::eval::EvalError; +// pub(crate) use self::uri::UriError; /// Built-in `Error` object. #[derive(Debug, Clone, Copy)] @@ -43,17 +46,11 @@ impl Error { pub(crate) const LENGTH: usize = 1; /// Create a new error object. - pub(crate) fn make_error(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - if !args.is_empty() { - this.set_field( - "message", - Value::from( - args.get(0) - .expect("failed getting error message") - .to_string(), - ), - ); + pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + if let Some(message) = args.get(0) { + this.set_field("message", ctx.to_string(message)?); } + // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) this.set_data(ObjectData::Error); diff --git a/boa/src/builtins/error/range.rs b/boa/src/builtins/error/range.rs index e39bdd16d8..04276281f7 100644 --- a/boa/src/builtins/error/range.rs +++ b/boa/src/builtins/error/range.rs @@ -32,17 +32,11 @@ impl RangeError { pub(crate) const LENGTH: usize = 1; /// Create a new error object. - pub(crate) fn make_error(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - if !args.is_empty() { - this.set_field( - "message", - Value::from( - args.get(0) - .expect("failed getting error message") - .to_string(), - ), - ); + pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + if let Some(message) = args.get(0) { + this.set_field("message", ctx.to_string(message)?); } + // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) this.set_data(ObjectData::Error); @@ -69,7 +63,8 @@ impl RangeError { /// Create a new `RangeError` object. pub(crate) fn create(global: &Value) -> Value { let prototype = Value::new_object(Some(global)); - prototype.set_field("message", Value::from("")); + prototype.set_field("name", Self::NAME); + prototype.set_field("message", ""); make_builtin_fn(Self::to_string, "toString", &prototype, 0); diff --git a/boa/src/builtins/error/reference.rs b/boa/src/builtins/error/reference.rs index 67befc5384..6c2edef05e 100644 --- a/boa/src/builtins/error/reference.rs +++ b/boa/src/builtins/error/reference.rs @@ -31,17 +31,11 @@ impl ReferenceError { pub(crate) const LENGTH: usize = 1; /// Create a new error object. - pub(crate) fn make_error(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - if !args.is_empty() { - this.set_field( - "message", - Value::from( - args.get(0) - .expect("failed getting error message") - .to_string(), - ), - ); + pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + if let Some(message) = args.get(0) { + this.set_field("message", ctx.to_string(message)?); } + // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) this.set_data(ObjectData::Error); @@ -68,7 +62,8 @@ impl ReferenceError { /// Create a new `ReferenceError` object. pub(crate) fn create(global: &Value) -> Value { let prototype = Value::new_object(Some(global)); - prototype.set_field("message", Value::from("")); + prototype.set_field("name", Self::NAME); + prototype.set_field("message", ""); make_builtin_fn(Self::to_string, "toString", &prototype, 0); diff --git a/boa/src/builtins/error/syntax.rs b/boa/src/builtins/error/syntax.rs new file mode 100644 index 0000000000..d7f954189a --- /dev/null +++ b/boa/src/builtins/error/syntax.rs @@ -0,0 +1,89 @@ +//! This module implements the global `SyntaxError` object. +//! +//! The SyntaxError object represents an error when trying to interpret syntactically invalid code. +//! It is thrown when the JavaScript engine encounters tokens or token order that does not conform +//! to the syntax of the language when parsing code. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript reference][spec] +//! +//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-syntaxerror +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError + +use crate::{ + builtins::{ + function::make_builtin_fn, + function::make_constructor_fn, + object::ObjectData, + value::{ResultValue, Value}, + }, + exec::Interpreter, + profiler::BoaProfiler, +}; +/// JavaScript `SyntaxError` impleentation. +#[derive(Debug, Clone, Copy)] +pub(crate) struct SyntaxError; + +impl SyntaxError { + /// The name of the object. + pub(crate) const NAME: &'static str = "SyntaxError"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: usize = 1; + + /// Create a new error object. + pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + if let Some(message) = args.get(0) { + this.set_field("message", ctx.to_string(message)?); + } + + // This value is used by console.log and other routines to match Object type + // to its Javascript Identifier (global constructor method name) + this.set_data(ObjectData::Error); + Err(this.clone()) + } + + /// `Error.prototype.toString()` + /// + /// The toString() method returns a string representing the specified Error object. + /// + /// More information: + /// - [MDN documentation][mdn] + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-error.prototype.tostring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + let name = this.get_field("name"); + let message = this.get_field("message"); + Ok(format!("{}: {}", name, message).into()) + } + + /// Create a new `SyntaxError` object. + pub(crate) fn create(global: &Value) -> Value { + let prototype = Value::new_object(Some(global)); + prototype.set_field("name", Self::NAME); + prototype.set_field("message", ""); + + make_builtin_fn(Self::to_string, "toString", &prototype, 0); + + make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_error, + global, + prototype, + true, + ) + } + + /// Initialise the global object with the `SyntaxError` object. + #[inline] + pub(crate) fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + (Self::NAME, Self::create(global)) + } +} diff --git a/boa/src/builtins/error/type.rs b/boa/src/builtins/error/type.rs index 54251416ba..23fe45b663 100644 --- a/boa/src/builtins/error/type.rs +++ b/boa/src/builtins/error/type.rs @@ -38,16 +38,9 @@ impl TypeError { pub(crate) const LENGTH: usize = 1; /// Create a new error object. - pub(crate) fn make_error(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - if !args.is_empty() { - this.set_field( - "message", - Value::from( - args.get(0) - .expect("failed getting error message") - .to_string(), - ), - ); + pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + if let Some(message) = args.get(0) { + this.set_field("message", ctx.to_string(message)?); } // This value is used by console.log and other routines to match Object type @@ -76,7 +69,8 @@ impl TypeError { /// Create a new `RangeError` object. pub(crate) fn create(global: &Value) -> Value { let prototype = Value::new_object(Some(global)); - prototype.set_field("message", Value::from("")); + prototype.set_field("name", Self::NAME); + prototype.set_field("message", ""); make_builtin_fn(Self::to_string, "toString", &prototype, 0); diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index 87514815cb..46ba43b777 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -24,7 +24,7 @@ pub(crate) use self::{ array::Array, bigint::BigInt, boolean::Boolean, - error::{Error, RangeError, ReferenceError, TypeError}, + error::{Error, RangeError, ReferenceError, SyntaxError, TypeError}, global_this::GlobalThis, infinity::Infinity, json::Json, @@ -60,6 +60,7 @@ pub fn init(global: &Value) { RangeError::init, ReferenceError::init, TypeError::init, + SyntaxError::init, // Global properties. NaN::init, Infinity::init, diff --git a/boa/src/exec/exception.rs b/boa/src/exec/exception.rs index 504872ed68..fba9596087 100644 --- a/boa/src/exec/exception.rs +++ b/boa/src/exec/exception.rs @@ -72,4 +72,25 @@ impl Interpreter { { Err(self.construct_reference_error(message)) } + + /// Constructs a `SyntaxError` with the specified message. + pub fn construct_syntax_error(&mut self, message: M) -> Value + where + M: Into, + { + New::from(Call::new( + Identifier::from("SyntaxError"), + vec![Const::from(message.into()).into()], + )) + .run(self) + .expect_err("SyntaxError should always throw") + } + + /// Throws a `SyntaxError` with the specified message. + pub fn throw_syntax_error(&mut self, message: M) -> ResultValue + where + M: Into, + { + Err(self.construct_syntax_error(message)) + } }