From bda00b06408d2d65315100751bfa5abd6c0e8a42 Mon Sep 17 00:00:00 2001 From: Kevin Putera Date: Sat, 2 Oct 2021 17:56:59 +0800 Subject: [PATCH] Implement Object.getOwnPropertyNames and Object.getOwnPropertySymbols (#1606) * Implement Object.getOwnPropertyNames and Object.getOwnPropertySymbols * Add documentation * Simplify implementation + add more docs following spec * Tests for Object.getOwnPropertyNames * Reduce duplication in test * Use new TestAction API for tests * Tests for Object.getOwnPropertySymbols * Simplify tests * Prevent unnecessary copies in get_own_property_keys --- boa/src/builtins/object/mod.rs | 79 ++++++++++++++++++++++++++++++++ boa/src/builtins/object/tests.rs | 70 +++++++++++++++++++++++++++- 2 files changed, 148 insertions(+), 1 deletion(-) diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 58b5174b36..35e384fa0c 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -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()) @@ -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 { + // 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 { + // 1. Return ? GetOwnPropertyKeys(O, symbol). + let o = args.get_or_undefined(0); + get_own_property_keys(o, PropertyKeyType::Symbol, context) + } } /// The abstract operation ObjectDefineProperties @@ -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 { + // 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()) +} diff --git a/boa/src/builtins/object/tests.rs b/boa/src/builtins/object/tests.rs index 6fb4bd8e80..01e68f0628 100644 --- a/boa/src/builtins/object/tests.rs +++ b/boa/src/builtins/object/tests.rs @@ -1,4 +1,4 @@ -use crate::{forward, Context, JsValue}; +use crate::{check_output, forward, Context, JsValue, TestAction}; #[test] fn object_create_with_regular_object() { @@ -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() { + 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) ]", + ), + ]); +}