Repository: boa-dev/boa (rust)

Issue:
Boa can't parse a class with a keyword as name
<!--
Thank you for reporting a bug in Boa! This will make us improve the engine. But first, fill the following template so that we better understand what's happening. Feel free to add or remove sections as you feel appropriate.
-->

**Describe the bug**
Boa can't parse a file containing `class of {}`. I found such a javascript file in some minified JS code.

<!-- E.g.:
The variable statement is not working as expected, it always adds 10 when assigning a number to a variable"
-->

**To Reproduce**
Steps to reproduce the issue, or JavaScript code that causes this failure.

This JavaScript code reproduces the issue:
```javascript
class of {}
```

**Expected behavior**
A class with name `of` should be created, like in browsers and node.

**Build environment (please complete the following information):**

- OS: Arch Linux
- Version: 0.20.0
- Target triple: `x86-unknown-linux-gnu`
- Rustc version: rustc 1.87.0 (17067e9ac 2025-05-09)

**Additional context**
N/a

<!-- E.g.:
You can find more information in [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var).
-->


Pull request:
Allow non-reserved keywords to be used as identifiers
<!---
Thank you for contributing to Boa! Please fill out the template below, and remove or add any
information as you feel necessary.
--->

This Pull Request fixes/closes #4298.

It changes the following:

- Added the `is_reserved_keyword` method to `Keyword` to determine if it is a reserved word.
- Added a check for non-reserved words when parsing class names.
- Added unit tests.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 9ec43378..8a45caca 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -792,9 +792,12 @@ dependencies = [
 
 [[package]]
 name = "cobs"
-version = "0.2.3"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"
+checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
+dependencies = [
+ "thiserror",
+]
 
 [[package]]
 name = "color-eyre"
@@ -1028,9 +1031,9 @@ dependencies = [
 
 [[package]]
 name = "crunchy"
-version = "0.2.3"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
 
 [[package]]
 name = "curl"
@@ -2100,9 +2103,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
 
 [[package]]
 name = "indexmap"
-version = "2.9.0"
+version = "2.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
+checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
 dependencies = [
  "equivalent",
  "hashbrown 0.15.4",
@@ -2281,9 +2284,9 @@ dependencies = [
 
 [[package]]
 name = "libredox"
-version = "0.1.3"
+version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
+checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638"
 dependencies = [
  "bitflags 2.9.1",
  "libc",
@@ -2885,9 +2888,9 @@ dependencies = [
 
 [[package]]
 name = "postcard"
-version = "1.1.1"
+version = "1.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8"
+checksum = "6c1de96e20f51df24ca73cafcc4690e044854d803259db27a00a461cb3b9d17a"
 dependencies = [
  "cobs",
  "embedded-io 0.4.0",
@@ -3711,9 +3714,9 @@ dependencies = [
 
 [[package]]
 name = "timezone_provider"
-version = "0.0.9"
+version = "0.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "332757a387cb7e6ce264143e9a103af85c96b3d0fc2fc2126579b52a3d7d95d2"
+checksum = "8b0b238b60524fc66d3d334e7fb6e89a881a6d4964f5462190bcfa1b61f3e7fa"
 dependencies = [
  "zerotrie",
  "zerovec",
diff --git a/core/engine/Cargo.toml b/core/engine/Cargo.toml
index 9d8ef378..2fcaba7a 100644
--- a/core/engine/Cargo.toml
+++ b/core/engine/Cargo.toml
@@ -12,6 +12,14 @@ repository.workspace = true
 rust-version.workspace = true
 
 [features]
+# Replace the NaN-boxing implementation of `JsValueInner` with an
+# enum-based implementation. This implementation is less performant
+# but compatible with more platforms.
+# If you encounter the "assertion left == right failed: Pointer is
+# not 4-bits aligned or over 51-bits." error, try this feature.
+# For more details, see https://github.com/boa-dev/boa/issues/4275
+# Disabled by default.
+jsvalue-enum = []
 deser = ["boa_interner/serde", "boa_ast/serde"]
 either = ["dep:either"]
 
diff --git a/core/engine/src/bytecompiler/mod.rs b/core/engine/src/bytecompiler/mod.rs
index 8811ad80..56f1dd5d 100644
--- a/core/engine/src/bytecompiler/mod.rs
+++ b/core/engine/src/bytecompiler/mod.rs
@@ -1995,12 +1995,11 @@ impl<'ctx> ByteCompiler<'ctx> {
         }
         self.r#return(false);
 
-        let mapped_arguments_binding_indices = self
-            .emitted_mapped_arguments_object_opcode
-            .then(|| {
-                MappedArguments::binding_indices(&self.params, &self.parameter_scope, self.interner)
-            })
-            .unwrap_or_default();
+        let mapped_arguments_binding_indices = if self.emitted_mapped_arguments_object_opcode {
+            MappedArguments::binding_indices(&self.params, &self.parameter_scope, self.interner)
+        } else {
+            ThinVec::default()
+        };
 
         let register_count = self.register_allocator.finish();
 
diff --git a/core/engine/src/object/jsobject.rs b/core/engine/src/object/jsobject.rs
index c55819c6..53377119 100644
--- a/core/engine/src/object/jsobject.rs
+++ b/core/engine/src/object/jsobject.rs
@@ -285,7 +285,7 @@ impl JsObject {
     #[must_use]
     #[track_caller]
     pub fn is_array(&self) -> bool {
-        std::ptr::eq(self.vtable(), &ARRAY_EXOTIC_INTERNAL_METHODS)
+        std::ptr::eq(self.vtable(), &raw const ARRAY_EXOTIC_INTERNAL_METHODS)
     }
 
     /// The inner implementation of `deep_strict_equals`, which keeps a list of values we've
diff --git a/core/engine/src/realm.rs b/core/engine/src/realm.rs
index 5ffc60bc..f57fafd6 100644
--- a/core/engine/src/realm.rs
+++ b/core/engine/src/realm.rs
@@ -222,7 +222,7 @@ impl Realm {
     }
 
     pub(crate) fn addr(&self) -> *const () {
-        let ptr: *const _ = &*self.inner;
+        let ptr: *const _ = &raw const *self.inner;
         ptr.cast()
     }
 }
diff --git a/core/engine/src/value/inner.rs b/core/engine/src/value/inner.rs
index bf6c00a3..1ec1aadf 100644
--- a/core/engine/src/value/inner.rs
+++ b/core/engine/src/value/inner.rs
@@ -1,3 +1,12 @@
 //! Module implementing the operations for the inner value of a `[super::JsValue]`.
-mod nan_boxed;
-pub(crate) use nan_boxed::NanBoxedValue as InnerValue;
+use cfg_if::cfg_if;
+
+cfg_if!(
+    if #[cfg(feature = "jsvalue-enum")] {
+        mod legacy;
+        pub(crate) use legacy::EnumBasedValue as InnerValue;
+    } else {
+        mod nan_boxed;
+        pub(crate) use nan_boxed::NanBoxedValue as InnerValue;
+    }
+);
diff --git a/core/engine/src/value/inner/legacy.rs b/core/engine/src/value/inner/legacy.rs
new file mode 100644
index 00000000..b2e079fd
--- /dev/null
+++ b/core/engine/src/value/inner/legacy.rs
@@ -0,0 +1,254 @@
+//! This `[JsValue]` inner type is an opaque enum implementing the same
+//! interface as the `NanBoxedValue` type, but using an enum instead of
+//! a 64-bits float.
+
+use crate::{JsBigInt, JsObject, JsSymbol};
+use boa_engine::JsVariant;
+use boa_gc::{custom_trace, Finalize, Trace};
+use boa_string::JsString;
+
+#[derive(Clone, Debug)]
+pub(crate) enum EnumBasedValue {
+    Undefined,
+    Null,
+    Boolean(bool),
+    Integer32(i32),
+    Float64(f64),
+    BigInt(JsBigInt),
+    Object(JsObject),
+    Symbol(JsSymbol),
+    String(JsString),
+}
+
+impl Finalize for EnumBasedValue {
+    fn finalize(&self) {
+        if let Some(o) = self.as_object() {
+            o.finalize();
+        }
+    }
+}
+
+#[allow(unsafe_op_in_unsafe_fn)]
+unsafe impl Trace for EnumBasedValue {
+    custom_trace! {this, mark, {
+        if let Some(o) = this.as_object() {
+            mark(o);
+        }
+    }}
+}
+
+impl EnumBasedValue {
+    /// Returns a `InnerValue` from a Null.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn null() -> Self {
+        Self::Null
+    }
+
+    /// Returns a `InnerValue` from an undefined.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn undefined() -> Self {
+        Self::Undefined
+    }
+
+    /// Returns a `InnerValue` from a 64-bits float. If the float is `NaN`,
+    /// it will be reduced to a canonical `NaN` representation.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn float64(value: f64) -> Self {
+        Self::Float64(value)
+    }
+
+    /// Returns a `InnerValue` from a 32-bits integer.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn integer32(value: i32) -> Self {
+        Self::Integer32(value)
+    }
+
+    /// Returns a `InnerValue` from a boolean.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn boolean(value: bool) -> Self {
+        Self::Boolean(value)
+    }
+
+    /// Returns a `InnerValue` from a boxed `[JsBigInt]`.
+    #[must_use]
+    #[inline]
+    pub(crate) fn bigint(value: JsBigInt) -> Self {
+        Self::BigInt(value)
+    }
+
+    /// Returns a `InnerValue` from a boxed `[JsObject]`.
+    #[must_use]
+    #[inline]
+    pub(crate) fn object(value: JsObject) -> Self {
+        Self::Object(value)
+    }
+
+    /// Returns a `InnerValue` from a boxed `[JsSymbol]`.
+    #[must_use]
+    #[inline]
+    pub(crate) fn symbol(value: JsSymbol) -> Self {
+        Self::Symbol(value)
+    }
+
+    /// Returns a `InnerValue` from a boxed `[JsString]`.
+    #[must_use]
+    #[inline]
+    pub(crate) fn string(value: JsString) -> Self {
+        Self::String(value)
+    }
+
+    /// Returns true if a value is undefined.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn is_undefined(&self) -> bool {
+        matches!(self, Self::Undefined)
+    }
+
+    /// Returns true if a value is null.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn is_null(&self) -> bool {
+        matches!(self, Self::Null)
+    }
+
+    /// Returns true if a value is a boolean.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn is_bool(&self) -> bool {
+        matches!(self, Self::Boolean(_))
+    }
+
+    /// Returns true if a value is a 64-bits float.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn is_float64(&self) -> bool {
+        matches!(self, Self::Float64(_))
+    }
+
+    /// Returns true if a value is a 32-bits integer.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn is_integer32(&self) -> bool {
+        matches!(self, Self::Integer32(_))
+    }
+
+    /// Returns true if a value is a `[JsBigInt]`. A `NaN` will not match here.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn is_bigint(&self) -> bool {
+        matches!(self, Self::BigInt(_))
+    }
+
+    /// Returns true if a value is a boxed Object.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn is_object(&self) -> bool {
+        matches!(self, Self::Object(_))
+    }
+
+    /// Returns true if a value is a boxed Symbol.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn is_symbol(&self) -> bool {
+        matches!(self, Self::Symbol(_))
+    }
+
+    /// Returns true if a value is a boxed String.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn is_string(&self) -> bool {
+        matches!(self, Self::String(_))
+    }
+
+    /// Returns the value as an f64 if it is a float.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn as_float64(&self) -> Option<f64> {
+        match self {
+            Self::Float64(value) => Some(*value),
+            _ => None,
+        }
+    }
+
+    /// Returns the value as an i32 if it is an integer.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn as_integer32(&self) -> Option<i32> {
+        match self {
+            Self::Integer32(value) => Some(*value),
+            _ => None,
+        }
+    }
+
+    /// Returns the value as a boolean if it is a boolean.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn as_bool(&self) -> Option<bool> {
+        match self {
+            Self::Boolean(value) => Some(*value),
+            _ => None,
+        }
+    }
+
+    /// Returns the value as a boxed `[JsBigInt]`.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn as_bigint(&self) -> Option<&JsBigInt> {
+        match self {
+            Self::BigInt(value) => Some(value),
+            _ => None,
+        }
+    }
+
+    /// Returns the value as a boxed `[JsObject]`.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn as_object(&self) -> Option<&JsObject> {
+        match self {
+            Self::Object(value) => Some(value),
+            _ => None,
+        }
+    }
+
+    /// Returns the value as a boxed `[JsSymbol]`.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn as_symbol(&self) -> Option<&JsSymbol> {
+        match self {
+            Self::Symbol(value) => Some(value),
+            _ => None,
+        }
+    }
+
+    /// Returns the value as a boxed `[JsString]`.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn as_string(&self) -> Option<&JsString> {
+        match self {
+            Self::String(value) => Some(value),
+            _ => None,
+        }
+    }
+
+    /// Returns the `[JsVariant]` of this inner value.
+    #[must_use]
+    #[inline]
+    pub(crate) const fn as_variant(&self) -> JsVariant<'_> {
+        match self {
+            Self::Undefined => JsVariant::Undefined,
+            Self::Null => JsVariant::Null,
+            Self::Boolean(v) => JsVariant::Boolean(*v),
+            Self::Integer32(v) => JsVariant::Integer32(*v),
+            Self::Float64(v) => JsVariant::Float64(*v),
+            Self::BigInt(v) => JsVariant::BigInt(v),
+            Self::Object(v) => JsVariant::Object(v),
+            Self::Symbol(v) => JsVariant::Symbol(v),
+            Self::String(v) => JsVariant::String(v),
+        }
+    }
+}
diff --git a/core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs b/core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs
index 5369c219..c6815c1c 100644
--- a/core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs
+++ b/core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs
@@ -89,6 +89,10 @@ where
                 BindingIdentifier::new(self.allow_yield, self.allow_await)
                     .parse(cursor, interner)?
             }
+            TokenKind::Keyword((k, _)) if !k.to_sym().is_reserved_identifier() => {
+                BindingIdentifier::new(self.allow_yield, self.allow_await)
+                    .parse(cursor, interner)?
+            }
             _ if self.is_default.0 => Identifier::new(Sym::DEFAULT, span),
             _ => {
                 return Err(Error::unexpected(
diff --git a/core/runtime/src/url.rs b/core/runtime/src/url.rs
index 2e07b8c1..7573a1d0 100644
--- a/core/runtime/src/url.rs
+++ b/core/runtime/src/url.rs
@@ -10,15 +10,15 @@
 //! [spec]: https://url.spec.whatwg.org/
 //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/URL
 #![cfg(feature = "url")]
+#![allow(clippy::needless_pass_by_value)]
 
 #[cfg(test)]
 mod tests;
 
+use boa_engine::class::Class;
 use boa_engine::value::Convert;
-use boa_engine::{
-    js_error, js_string, Context, Finalize, JsData, JsResult, JsString, JsValue, Trace,
-};
-use boa_interop::{js_class, IntoJsFunctionCopied, JsClass};
+use boa_engine::{js_error, Context, Finalize, JsData, JsResult, JsString, JsValue, Trace};
+use boa_interop::boa_macros::boa_class;
 use std::fmt::Display;
 
 /// The `URL` class represents a (properly parsed) Uniform Resource Locator.
@@ -35,13 +35,36 @@ impl Url {
         context.register_global_class::<Self>()?;
         Ok(())
     }
+}
+
+impl Display for Url {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl From<url::Url> for Url {
+    fn from(url: url::Url) -> Self {
+        Self(url)
+    }
+}
+
+impl From<Url> for url::Url {
+    fn from(url: Url) -> url::Url {
+        url.0
+    }
+}
 
+#[boa_class(rename = "URL")]
+#[boa(rename_all = "camelCase")]
+impl Url {
     /// Create a new `URL` object. Meant to be called from the JavaScript constructor.
     ///
     /// # Errors
     /// Any errors that might occur during URL parsing.
-    fn js_new(Convert(ref url): Convert<String>, base: Option<&Convert<String>>) -> JsResult<Self> {
-        if let Some(Convert(base)) = base {
+    #[boa(constructor)]
+    pub fn new(Convert(ref url): Convert<String>, base: Option<Convert<String>>) -> JsResult<Self> {
+        if let Some(Convert(ref base)) = base {
             let base_url = url::Url::parse(base)
                 .map_err(|e| js_error!(TypeError: "Failed to parse base URL: {}", e))?;
             if base_url.cannot_be_a_base() {
@@ -58,179 +81,159 @@ impl Url {
             Ok(Self(url))
         }
     }
-}
 
-impl Display for Url {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.0)
+    #[boa(getter)]
+    fn hash(&self) -> JsString {
+        JsString::from(url::quirks::hash(&self.0))
     }
-}
 
-impl From<url::Url> for Url {
-    fn from(url: url::Url) -> Self {
-        Self(url)
+    #[boa(setter)]
+    #[boa(rename = "hash")]
+    fn set_hash(&mut self, value: Convert<String>) {
+        url::quirks::set_hash(&mut self.0, &value.0);
     }
-}
 
-impl From<Url> for url::Url {
-    fn from(url: Url) -> url::Url {
-        url.0
+    #[boa(getter)]
+    fn hostname(&self) -> JsString {
+        JsString::from(url::quirks::hostname(&self.0))
     }
-}
 
-js_class! {
-    class Url as "URL" {
-        property hash {
-            fn get(this: JsClass<Url>) -> JsString {
-                JsString::from(url::quirks::hash(&this.borrow().0))
-            }
-
-            fn set(this: JsClass<Url>, value: Convert<String>) {
-                url::quirks::set_hash(&mut this.borrow_mut().0, &value.0);
-            }
-        }
+    #[boa(setter)]
+    #[boa(rename = "hostname")]
+    fn set_hostname(&mut self, value: Convert<String>) {
+        let _ = url::quirks::set_hostname(&mut self.0, &value.0);
+    }
 
-        property hostname {
-            fn get(this: JsClass<Url>) -> JsString {
-                JsString::from(url::quirks::hostname(&this.borrow().0))
-            }
+    #[boa(getter)]
+    fn host(&self) -> JsString {
+        JsString::from(url::quirks::host(&self.0))
+    }
 
-            fn set(this: JsClass<Url>, value: Convert<String>) {
-                let _ = url::quirks::set_hostname(&mut this.borrow_mut().0, &value.0);
-            }
-        }
+    #[boa(setter)]
+    #[boa(rename = "host")]
+    fn set_host(&mut self, value: Convert<String>) {
+        let _ = url::quirks::set_host(&mut self.0, &value.0);
+    }
 
-        property host {
-            fn get(this: JsClass<Url>) -> JsString {
-                JsString::from(url::quirks::host(&this.borrow().0))
-            }
+    #[boa(getter)]
+    fn href(&self) -> JsString {
+        JsString::from(url::quirks::href(&self.0))
+    }
 
-            fn set(this: JsClass<Url>, value: Convert<String>) {
-                let _ = url::quirks::set_host(&mut this.borrow_mut().0, &value.0);
-            }
-        }
+    #[boa(setter)]
+    #[boa(rename = "href")]
+    fn set_href(&mut self, value: Convert<String>) -> JsResult<()> {
+        url::quirks::set_href(&mut self.0, &value.0)
+            .map_err(|e| js_error!(TypeError: "Failed to set href: {}", e))
+    }
 
-        property href {
-            fn get(this: JsClass<Url>) -> JsString {
-                JsString::from(url::quirks::href(&this.borrow().0))
-            }
+    #[boa(getter)]
+    fn origin(&self) -> JsString {
+        JsString::from(url::quirks::origin(&self.0))
+    }
 
-            fn set(this: JsClass<Url>, value: Convert<String>) -> JsResult<()> {
-                url::quirks::set_href(&mut this.borrow_mut().0, &value.0)
-                    .map_err(|e| js_error!(TypeError: "Failed to set href: {}", e))
-            }
-        }
+    #[boa(getter)]
+    fn password(&self) -> JsString {
+        JsString::from(url::quirks::password(&self.0))
+    }
 
-        property origin {
-            fn get(this: JsClass<Url>) -> JsString {
-                JsString::from(url::quirks::origin(&this.borrow().0))
-            }
-        }
+    #[boa(setter)]
+    #[boa(rename = "password")]
+    fn set_password(&mut self, value: Convert<String>) {
+        let _ = url::quirks::set_password(&mut self.0, &value.0);
+    }
 
-        property password {
-            fn get(this: JsClass<Url>) -> JsString {
-                JsString::from(url::quirks::password(&this.borrow().0))
-            }
+    #[boa(getter)]
+    fn pathname(&self) -> JsString {
+        JsString::from(url::quirks::pathname(&self.0))
+    }
 
-            fn set(this: JsClass<Url>, value: Convert<String>) {
-                let _ = url::quirks::set_password(&mut this.borrow_mut().0, &value.0);
-            }
-        }
+    #[boa(setter)]
+    #[boa(rename = "pathname")]
+    fn set_pathname(&mut self, value: Convert<String>) {
+        let () = url::quirks::set_pathname(&mut self.0, &value.0);
+    }
 
-        property pathname {
-            fn get(this: JsClass<Url>) -> JsString {
-                JsString::from(url::quirks::pathname(&this.borrow().0))
-            }
+    #[boa(getter)]
+    fn port(&self) -> JsString {
+        JsString::from(url::quirks::port(&self.0))
+    }
 
-            fn set(this: JsClass<Url>, value: Convert<String>) {
-                let () = url::quirks::set_pathname(&mut this.borrow_mut().0, &value.0);
-            }
-        }
+    #[boa(setter)]
+    #[boa(rename = "port")]
+    fn set_port(&mut self, value: Convert<JsString>) {
+        let _ = url::quirks::set_port(&mut self.0, &value.0.to_std_string_lossy());
+    }
 
-        property port {
-            fn get(this: JsClass<Url>) -> JsString {
-                JsString::from(url::quirks::port(&this.borrow().0))
-            }
+    #[boa(getter)]
+    fn protocol(&self) -> JsString {
+        JsString::from(url::quirks::protocol(&self.0))
+    }
 
-            fn set(this: JsClass<Url>, value: Convert<JsString>) {
-                let _ = url::quirks::set_port(&mut this.borrow_mut().0, &value.0.to_std_string_lossy());
-            }
-        }
+    #[boa(setter)]
+    #[boa(rename = "protocol")]
+    fn set_protocol(&mut self, value: Convert<String>) {
+        let _ = url::quirks::set_protocol(&mut self.0, &value.0);
+    }
 
-        property protocol {
-            fn get(this: JsClass<Url>) -> JsString {
-                JsString::from(url::quirks::protocol(&this.borrow().0))
-            }
+    #[boa(getter)]
+    fn search(&self) -> JsString {
+        JsString::from(url::quirks::search(&self.0))
+    }
 
-            fn set(this: JsClass<Url>, value: Convert<String>) {
-                let _ = url::quirks::set_protocol(&mut this.borrow_mut().0, &value.0);
-            }
-        }
+    #[boa(setter)]
+    #[boa(rename = "search")]
+    fn set_search(&mut self, value: Convert<String>) {
+        url::quirks::set_search(&mut self.0, &value.0);
+    }
 
-        property search {
-            fn get(this: JsClass<Url>) -> JsString {
-                JsString::from(url::quirks::search(&this.borrow().0))
-            }
+    #[boa(getter)]
+    fn search_params() -> JsResult<()> {
+        Err(js_error!(Error: "URL.searchParams is not implemented"))
+    }
 
-            fn set(this: JsClass<Url>, value: Convert<String>) {
-                url::quirks::set_search(&mut this.borrow_mut().0, &value.0);
-            }
-        }
+    #[boa(getter)]
+    fn username(&self) -> JsString {
+        JsString::from(self.0.username())
+    }
 
-        property search_params as "searchParams" {
-            fn get() -> JsResult<()> {
-                Err(js_error!(Error: "URL.searchParams is not implemented"))
-            }
-        }
+    #[boa(setter)]
+    #[boa(rename = "username")]
+    fn set_username(&mut self, value: Convert<String>) {
+        let _ = self.0.set_username(&value.0);
+    }
 
-        property username {
-            fn get(this: JsClass<Url>) -> JsString {
-                JsString::from(this.borrow().0.username())
-            }
+    fn to_string(&self) -> JsString {
+        JsString::from(format!("{}", self.0))
+    }
 
-            fn set(this: JsClass<Url>, value: Convert<String>) {
-                let _ = this.borrow_mut().0.set_username(&value.0);
-            }
-        }
+    fn to_json(&self) -> JsString {
+        JsString::from(format!("{}", self.0))
+    }
 
-        constructor(url: Convert<String>, base: Option<Convert<String>>) {
-            Self::js_new(url, base.as_ref())
-        }
+    #[boa(static)]
+    fn create_object_url() -> JsResult<()> {
+        Err(js_error!(Error: "URL.createObjectURL is not implemented"))
+    }
 
-        init(class: &mut ClassBuilder) -> JsResult<()> {
-            let create_object_url = (|| -> JsResult<()> {
-                    Err(js_error!(Error: "URL.createObjectURL is not implemented"))
-                })
-                .into_js_function_copied(class.context());
-            let can_parse = (|url: Convert<String>, base: Option<Convert<String>>| {
-                    Url::js_new(url, base.as_ref()).is_ok()
-                })
-                .into_js_function_copied(class.context());
-            let parse = (|url: Convert<String>, base: Option<Convert<String>>, context: &mut Context| {
-                    Url::js_new(url, base.as_ref())
-                        .map_or(Ok(JsValue::null()), |u| Url::from_data(u, context).map(JsValue::from))
-                })
-                .into_js_function_copied(class.context());
-            let revoke_object_url = (|| -> JsResult<()> {
-                    Err(js_error!(Error: "URL.revokeObjectURL is not implemented"))
-                })
-                .into_js_function_copied(class.context());
-
-            class
-                .static_method(js_string!("createObjectURL"), 1, create_object_url)
-                .static_method(js_string!("canParse"), 2, can_parse)
-                .static_method(js_string!("parse"), 2, parse)
-                .static_method(js_string!("revokeObjectUrl"), 1, revoke_object_url);
-
-            Ok(())
-        }
+    #[boa(static)]
+    fn can_parse(url: Convert<String>, base: Option<Convert<String>>) -> bool {
+        Url::new(url, base).is_ok()
+    }
 
-        fn to_string as "toString"(this: JsClass<Url>) -> JsString {
-            JsString::from(format!("{}", this.borrow().0))
-        }
+    #[boa(static)]
+    fn parse(
+        url: Convert<String>,
+        base: Option<Convert<String>>,
+        context: &mut Context,
+    ) -> JsResult<JsValue> {
+        Url::new(url, base).map_or(Ok(JsValue::null()), |u| {
+            Url::from_data(u, context).map(JsValue::from)
+        })
+    }
 
-        fn to_json as "toJSON"(this: JsClass<Url>) -> JsString {
-            JsString::from(format!("{}", this.borrow().0))
-        }
+    #[boa(static)]
+    fn revoke_object_url() -> JsResult<()> {
+        Err(js_error!(Error: "URL.revokeObjectURL is not implemented"))
     }
 }


Test patch:
diff --git a/core/parser/src/lexer/tests.rs b/core/parser/src/lexer/tests.rs
index 35cf36dd..86400098 100644
--- a/core/parser/src/lexer/tests.rs
+++ b/core/parser/src/lexer/tests.rs
@@ -960,11 +960,11 @@ fn string_codepoint_with_no_braces() {
 }
 
 #[test]
-#[ignore]
+#[ignore = "Need to assert if this test is valid"]
 fn illegal_code_point_following_numeric_literal() {
     // Checks as per https://tc39.es/ecma262/#sec-literals-numeric-literals that a NumericLiteral cannot
     // be immediately followed by an IdentifierStart where the IdentifierStart
-    let mut lexer = Lexer::from(&br"17.4\u{2764}"[..]);
+    let mut lexer = Lexer::from("17.4\u{2764}".as_bytes());
     let interner = &mut Interner::default();
 
     assert!(
diff --git a/core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs b/core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs
index aa5b2878..823cc6ee 100644
--- a/core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs
+++ b/core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs
@@ -230,3 +230,28 @@ fn check_new_target_with_property_access() {
         interner,
     );
 }
+
+#[test]
+fn check_non_reserved_keyword_as_identifier() {
+    let interner = &mut Interner::default();
+
+    check_script_parser(
+        indoc! {r#"
+            class of {}
+        "#},
+        [Declaration::ClassDeclaration(
+            ClassDeclaration::new(
+                Identifier::new(
+                    interner.get_or_intern_static("of", utf16!("of")),
+                    Span::new((1, 7), (1, 9)),
+                ),
+                None,
+                None,
+                vec![].into(),
+            )
+            .into(),
+        )
+        .into()],
+        interner,
+    );
+}
diff --git a/core/parser/src/parser/tests/mod.rs b/core/parser/src/parser/tests/mod.rs
index eafac469..526ced72 100644
--- a/core/parser/src/parser/tests/mod.rs
+++ b/core/parser/src/parser/tests/mod.rs
@@ -23,7 +23,7 @@ use boa_ast::{
         FunctionBody, FunctionDeclaration,
     },
     scope::Scope,
-    statement::{If, Return},
+    statement::{Block, If, Return, With},
     Expression, LinearPosition, LinearSpan, Module, ModuleItem, ModuleItemList, Script, Span,
     Statement, StatementList, StatementListItem,
 };
@@ -732,14 +732,17 @@ fn hashbang_use_strict_no_with() {
 }
 
 #[test]
-#[ignore]
 fn hashbang_use_strict_with_with_statement() {
     check_script_parser(
-        r#"#!\"use strict"
-
-        with({}) {}
-        "#,
-        vec![],
+        indoc! {r#"
+            #!\"use strict"
+            with({}) {}
+        "#},
+        vec![Statement::With(With::new(
+            ObjectLiteral::new([], Span::new((2, 6), (2, 8))).into(),
+            Block::from(StatementList::new([], LinearPosition::new(27), false)).into(),
+        ))
+        .into()],
         &mut Interner::default(),
     );
 }