Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Object.getOwnPropertyNames and Object.getOwnPropertySymbols #1606

Merged
79 changes: 79 additions & 0 deletions boa/src/builtins/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ impl BuiltIn for Object {
"getOwnPropertyDescriptors",
1,
)
.static_method(Self::get_own_property_names, "getOwnPropertyNames", 1)
.static_method(Self::get_own_property_symbols, "getOwnPropertySymbols", 1)
.build();

(Self::NAME, object.into(), Self::attribute())
Expand Down Expand Up @@ -824,6 +826,42 @@ impl Object {
Ok(JsValue::new(false))
}
}

/// `Object.getOwnPropertyNames( object )`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-object.getownpropertynames
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames
pub fn get_own_property_names(
_: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Return ? GetOwnPropertyKeys(O, string).
let o = args.get_or_undefined(0);
get_own_property_keys(o, PropertyKeyType::String, context)
}

/// `Object.getOwnPropertySymbols( object )`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-object.getownpropertysymbols
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols
pub fn get_own_property_symbols(
_: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Return ? GetOwnPropertyKeys(O, symbol).
let o = args.get_or_undefined(0);
get_own_property_keys(o, PropertyKeyType::Symbol, context)
}
}

/// The abstract operation ObjectDefineProperties
Expand Down Expand Up @@ -877,3 +915,44 @@ fn object_define_properties(
// 7. Return O.
Ok(())
}

/// Type enum used in the abstract operation GetOwnPropertyKeys
#[derive(Debug, Copy, Clone)]
enum PropertyKeyType {
String,
Symbol,
}

/// The abstract operation GetOwnPropertyKeys
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-getownpropertykeys
fn get_own_property_keys(
o: &JsValue,
r#type: PropertyKeyType,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let obj be ? ToObject(o).
let obj = o.to_object(context)?;

// 2. Let keys be ? obj.[[OwnPropertyKeys]]().
let keys = obj.__own_property_keys__(context)?;

// 3. Let nameList be a new empty List.
// 4. For each element nextKey of keys, do
let name_list = keys.iter().filter_map(|next_key| {
// a. If Type(nextKey) is Symbol and type is symbol or Type(nextKey) is String and type is string, then
// i. Append nextKey as the last element of nameList.
match (r#type, &next_key) {
(PropertyKeyType::String, PropertyKey::String(_)) => Some(next_key.into()),
(PropertyKeyType::String, PropertyKey::Index(index)) => Some(index.to_string().into()),
(PropertyKeyType::Symbol, PropertyKey::Symbol(_)) => Some(next_key.into()),
_ => None,
}
});

// 5. Return CreateArrayFromList(nameList).
Ok(Array::create_array_from_list(name_list, context).into())
}
70 changes: 69 additions & 1 deletion boa/src/builtins/object/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{forward, Context, JsValue};
use crate::{check_output, forward, Context, JsValue, TestAction};

#[test]
fn object_create_with_regular_object() {
Expand Down Expand Up @@ -289,3 +289,71 @@ fn object_is_prototype_of() {

assert_eq!(context.eval(init).unwrap(), JsValue::new(true));
}

#[test]
fn object_get_own_property_names_invalid_args() {
let error_message = r#"Uncaught "TypeError": "cannot convert 'null' or 'undefined' to object""#;

check_output(&[
TestAction::TestEq("Object.getOwnPropertyNames()", error_message),
TestAction::TestEq("Object.getOwnPropertyNames(null)", error_message),
TestAction::TestEq("Object.getOwnPropertyNames(undefined)", error_message),
]);
}

#[test]
fn object_get_own_property_names() {
jedel1043 marked this conversation as resolved.
Show resolved Hide resolved
check_output(&[
TestAction::TestEq("Object.getOwnPropertyNames(0)", "[]"),
TestAction::TestEq("Object.getOwnPropertyNames(false)", "[]"),
TestAction::TestEq(r#"Object.getOwnPropertyNames(Symbol("a"))"#, "[]"),
TestAction::TestEq("Object.getOwnPropertyNames({})", "[]"),
TestAction::TestEq("Object.getOwnPropertyNames(NaN)", "[]"),
TestAction::TestEq(
"Object.getOwnPropertyNames([1, 2, 3])",
r#"[ "0", "1", "2", "length" ]"#,
),
TestAction::TestEq(
r#"Object.getOwnPropertyNames({
"a": 1,
"b": 2,
[ Symbol("c") ]: 3,
[ Symbol("d") ]: 4,
})"#,
r#"[ "a", "b" ]"#,
),
]);
}

#[test]
fn object_get_own_property_symbols_invalid_args() {
let error_message = r#"Uncaught "TypeError": "cannot convert 'null' or 'undefined' to object""#;

check_output(&[
TestAction::TestEq("Object.getOwnPropertySymbols()", error_message),
TestAction::TestEq("Object.getOwnPropertySymbols(null)", error_message),
TestAction::TestEq("Object.getOwnPropertySymbols(undefined)", error_message),
]);
}

#[test]
fn object_get_own_property_symbols() {
check_output(&[
TestAction::TestEq("Object.getOwnPropertySymbols(0)", "[]"),
TestAction::TestEq("Object.getOwnPropertySymbols(false)", "[]"),
TestAction::TestEq(r#"Object.getOwnPropertySymbols(Symbol("a"))"#, "[]"),
TestAction::TestEq("Object.getOwnPropertySymbols({})", "[]"),
TestAction::TestEq("Object.getOwnPropertySymbols(NaN)", "[]"),
TestAction::TestEq("Object.getOwnPropertySymbols([1, 2, 3])", "[]"),
TestAction::TestEq(
r#"
Object.getOwnPropertySymbols({
"a": 1,
"b": 2,
[ Symbol("c") ]: 3,
[ Symbol("d") ]: 4,
})"#,
"[ Symbol(c), Symbol(d) ]",
),
]);
}