diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 61165faa412..3986c85fa40 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -18,12 +18,14 @@ use crate::{ object::{ ConstructorBuilder, Object as BuiltinObject, ObjectData, ObjectInitializer, PROTOTYPE, }, - property::{Attribute, DescriptorKind, PropertyDescriptor}, + property::{Attribute, DescriptorKind, PropertyDescriptor, PropertyNameKind}, symbol::WellKnownSymbols, value::{JsValue, Type}, BoaProfiler, Context, Result, }; +use super::Array; + pub mod for_in_iterator; #[cfg(test)] mod tests; @@ -61,6 +63,8 @@ impl BuiltIn for Object { .static_method(Self::define_properties, "defineProperties", 2) .static_method(Self::assign, "assign", 2) .static_method(Self::is, "is", 2) + .static_method(Self::keys, "keys", 1) + .static_method(Self::entries, "entries", 1) .static_method( Self::get_own_property_descriptor, "getOwnPropertyDescriptor", @@ -541,8 +545,6 @@ impl Object { /// [spec]: https://tc39.es/ecma262/#sec-object.assign /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign pub fn assign(_: &JsValue, args: &[JsValue], context: &mut Context) -> Result { - // - // // 1. Let to be ? ToObject(target). let to = args .get(0) @@ -582,4 +584,62 @@ impl Object { // 4. Return to. Ok(to.into()) } + + /// `Object.keys( target )` + /// + /// This method returns an array of a given object's own enumerable + /// property names, iterated in the same order that a normal loop would. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object.keys + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys + pub fn keys(_: &JsValue, args: &[JsValue], context: &mut Context) -> Result { + // 1. Let obj be ? ToObject(target). + let obj = args + .get(0) + .cloned() + .unwrap_or_default() + .to_object(context)?; + + // 2. Let nameList be ? EnumerableOwnPropertyNames(obj, key). + let name_list = obj.enumerable_own_property_names(PropertyNameKind::Key, context)?; + + // 3. Return CreateArrayFromList(nameList). + let result = Array::create_array_from_list(name_list, context); + + Ok(result.into()) + } + + /// `Object.entries( target )` + /// + /// This method returns an array of a given object's own enumerable string-keyed property [key, value] pairs. + /// This is the same as iterating with a for...in loop, + /// except that a for...in loop enumerates properties in the prototype chain as well). + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object.entries + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries + pub fn entries(_: &JsValue, args: &[JsValue], context: &mut Context) -> Result { + // 1. Let obj be ? ToObject(target). + let obj = args + .get(0) + .cloned() + .unwrap_or_default() + .to_object(context)?; + + // 2. Let nameList be ? EnumerableOwnPropertyNames(obj, key+value). + let name_list = + obj.enumerable_own_property_names(PropertyNameKind::KeyAndValue, context)?; + + // 3. Return CreateArrayFromList(nameList). + let result = Array::create_array_from_list(name_list, context); + + Ok(result.into()) + } } diff --git a/boa/src/object/internal_methods.rs b/boa/src/object/internal_methods.rs index 4d8a66072d1..f5474ae3598 100644 --- a/boa/src/object/internal_methods.rs +++ b/boa/src/object/internal_methods.rs @@ -6,8 +6,9 @@ //! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots use crate::{ + builtins::Array, object::{GcObject, Object, ObjectData}, - property::{DescriptorKind, PropertyDescriptor, PropertyKey}, + property::{DescriptorKind, PropertyDescriptor, PropertyKey, PropertyNameKind}, value::{JsValue, Type}, BoaProfiler, Context, Result, }; @@ -881,6 +882,65 @@ impl GcObject { Ok(list) } + /// It is used to iterate over names of object's keys. + /// + /// More information: + /// - [EcmaScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-enumerableownpropertynames + pub(crate) fn enumerable_own_property_names( + &self, + kind: PropertyNameKind, + context: &mut Context, + ) -> Result> { + // 1. Assert: Type(O) is Object. + // 2. Let ownKeys be ? O.[[OwnPropertyKeys]](). + let own_keys = self.own_property_keys(); + // 3. Let properties be a new empty List. + let mut properties = vec![]; + + // 4. For each element key of ownKeys, do + for key in own_keys { + // a. If Type(key) is String, then + if let PropertyKey::String(key_str) = &key { + // i. Let desc be ? O.[[GetOwnProperty]](key). + let desc = self.__get_own_property__(&key); + // ii. If desc is not undefined and desc.[[Enumerable]] is true, then + if let Some(desc) = desc { + if desc.expect_enumerable() { + // 1. If kind is key, append key to properties. + if let PropertyNameKind::Key = kind { + properties.push(key_str.clone().into()) + } + // 2. Else, + else { + // a. Let value be ? Get(O, key). + let value = self.get(key.clone(), context)?; + // b. If kind is value, append value to properties. + if let PropertyNameKind::Value = kind { + properties.push(value) + } + // c. Else, + else { + // i. Assert: kind is key+value. + // ii. Let entry be ! CreateArrayFromList(« key, value »). + let key_val = key_str.clone().into(); + let entry = + Array::create_array_from_list([key_val, value], context); + + // iii. Append entry to properties. + properties.push(entry.into()); + } + } + } + } + } + } + + // 5. Return properties. + Ok(properties) + } + pub(crate) fn length_of_array_like(&self, context: &mut Context) -> Result { // 1. Assert: Type(obj) is Object. // 2. Return ℝ(? ToLength(? Get(obj, "length"))). diff --git a/boa/src/property/mod.rs b/boa/src/property/mod.rs index b09b47f039e..48a92763ee9 100644 --- a/boa/src/property/mod.rs +++ b/boa/src/property/mod.rs @@ -662,3 +662,10 @@ impl PartialEq<&str> for PropertyKey { } } } + +#[derive(Debug, Clone, Copy)] +pub(crate) enum PropertyNameKind { + Key, + Value, + KeyAndValue, +}