use std::cell::RefCell;
use std::rc::Rc;
use crate::error::{StatorError, StatorResult};
use crate::objects::js_object::JsObject;
use crate::objects::map::PropertyAttributes;
use crate::objects::property_map::PropertyMap;
use crate::objects::value::JsValue;
pub fn reflect_get(target: &JsObject, key: &str) -> JsValue {
target.get_property(key)
}
pub fn reflect_get_with_receiver(target: &JsObject, key: &str, _receiver: &JsValue) -> JsValue {
target.get_property(key)
}
pub fn reflect_set(target: &mut JsObject, key: &str, value: JsValue) -> StatorResult<bool> {
Ok(target.set_property(key, value).is_ok())
}
pub fn reflect_set_with_receiver(
target: &mut JsObject,
key: &str,
value: JsValue,
_receiver: &JsValue,
) -> StatorResult<bool> {
Ok(target.set_property(key, value).is_ok())
}
pub fn reflect_has(target: &JsObject, key: &str) -> bool {
target.has_property(key)
}
pub fn reflect_delete_property(target: &mut JsObject, key: &str) -> StatorResult<bool> {
target.delete_own_property(key)
}
pub fn reflect_define_property(
target: &mut JsObject,
key: &str,
value: JsValue,
attributes: PropertyAttributes,
) -> StatorResult<bool> {
Ok(target.define_own_property(key, value, attributes).is_ok())
}
pub fn reflect_get_own_property_descriptor(
target: &JsObject,
key: &str,
) -> Option<(JsValue, PropertyAttributes)> {
target.get_own_property_descriptor(key)
}
pub fn reflect_get_prototype_of(target: &JsObject) -> Option<Rc<RefCell<JsObject>>> {
target.prototype().cloned()
}
pub fn reflect_set_prototype_of(
target: &mut JsObject,
proto: Option<Rc<RefCell<JsObject>>>,
) -> bool {
use crate::builtins::object::object_set_prototype_of;
object_set_prototype_of(target, proto).is_ok()
}
pub fn reflect_is_extensible(target: &JsObject) -> bool {
target.is_extensible()
}
pub fn reflect_prevent_extensions(target: &mut JsObject) -> bool {
target.prevent_extensions();
true
}
pub fn reflect_own_keys(target: &JsObject) -> Vec<String> {
target.own_property_keys()
}
pub fn reflect_apply(
target: impl Fn(JsValue, Vec<JsValue>) -> crate::error::StatorResult<JsValue>,
this_argument: JsValue,
arguments_list: Vec<JsValue>,
) -> crate::error::StatorResult<JsValue> {
target(this_argument, arguments_list)
}
pub fn reflect_construct(
target: impl Fn(Vec<JsValue>) -> crate::error::StatorResult<JsObject>,
arguments_list: Vec<JsValue>,
) -> crate::error::StatorResult<JsObject> {
target(arguments_list)
}
pub fn reflect_construct_with_new_target(
target: impl Fn(Vec<JsValue>) -> crate::error::StatorResult<JsObject>,
arguments_list: Vec<JsValue>,
new_target_proto: Option<Rc<RefCell<JsObject>>>,
) -> crate::error::StatorResult<JsObject> {
let mut result = target(arguments_list)?;
if let Some(proto) = new_target_proto {
result.set_prototype(Some(proto));
}
Ok(result)
}
pub fn reflect_get_prototype_of_value(target: &JsObject) -> JsValue {
match target.prototype() {
Some(proto) => {
let borrowed = proto.borrow();
let mut map = PropertyMap::new();
for key in borrowed.own_property_keys() {
if let Some(val) = borrowed.get_own_property(&key) {
map.insert(key, val);
}
}
JsValue::PlainObject(Rc::new(RefCell::new(map)))
}
None => JsValue::Null,
}
}
pub fn reflect_set_prototype_of_value(target: &mut JsObject, proto: JsValue) -> StatorResult<bool> {
use crate::builtins::object::object_set_prototype_of;
if matches!(proto, JsValue::Null) {
Ok(object_set_prototype_of(target, None).is_ok())
} else if proto.is_object_like() {
let mut js_obj = JsObject::new();
if let JsValue::PlainObject(map) = &proto {
let borrowed = map.borrow();
for (key, val) in borrowed.iter() {
let _ = js_obj.set_property(key, val.clone());
}
}
Ok(object_set_prototype_of(target, Some(Rc::new(RefCell::new(js_obj)))).is_ok())
} else {
Err(StatorError::TypeError(
"Object prototype may only be an Object or null".to_string(),
))
}
}
pub fn reflect_own_keys_values(target: &JsObject) -> Vec<JsValue> {
target
.own_property_keys()
.into_iter()
.map(|key| {
if let Some(id) = crate::builtins::symbol::property_key_to_symbol(&key) {
JsValue::Symbol(id)
} else {
JsValue::String(key.into())
}
})
.collect()
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use std::rc::Rc;
use super::*;
use crate::objects::js_object::JsObject;
use crate::objects::map::PropertyAttributes;
use crate::objects::value::JsValue;
#[test]
fn test_reflect_get_own_property() {
let mut t = JsObject::new();
t.set_property("x", JsValue::Smi(5)).unwrap();
assert_eq!(reflect_get(&t, "x"), JsValue::Smi(5));
}
#[test]
fn test_reflect_get_missing_returns_undefined() {
let t = JsObject::new();
assert_eq!(reflect_get(&t, "nope"), JsValue::Undefined);
}
#[test]
fn test_reflect_get_walks_prototype_chain() {
let proto = Rc::new(RefCell::new(JsObject::new()));
proto
.borrow_mut()
.set_property("inherited", JsValue::Smi(10))
.unwrap();
let child = JsObject::with_prototype(proto);
assert_eq!(reflect_get(&child, "inherited"), JsValue::Smi(10));
}
#[test]
fn test_reflect_get_with_receiver_returns_value() {
let mut t = JsObject::new();
t.set_property("k", JsValue::Smi(77)).unwrap();
let receiver = JsValue::Undefined;
assert_eq!(
reflect_get_with_receiver(&t, "k", &receiver),
JsValue::Smi(77)
);
}
#[test]
fn test_reflect_get_with_receiver_missing_returns_undefined() {
let t = JsObject::new();
let receiver = JsValue::Undefined;
assert_eq!(
reflect_get_with_receiver(&t, "nope", &receiver),
JsValue::Undefined
);
}
#[test]
fn test_reflect_set_creates_property() {
let mut t = JsObject::new();
assert!(reflect_set(&mut t, "k", JsValue::Smi(99)).unwrap());
assert_eq!(t.get_property("k"), JsValue::Smi(99));
}
#[test]
fn test_reflect_set_non_writable_returns_false() {
let mut t = JsObject::new();
t.define_own_property("ro", JsValue::Smi(1), PropertyAttributes::empty())
.unwrap();
assert!(!reflect_set(&mut t, "ro", JsValue::Smi(2)).unwrap());
}
#[test]
fn test_reflect_set_with_receiver_creates_property() {
let mut t = JsObject::new();
let receiver = JsValue::Undefined;
assert!(reflect_set_with_receiver(&mut t, "k", JsValue::Smi(50), &receiver).unwrap());
assert_eq!(t.get_property("k"), JsValue::Smi(50));
}
#[test]
fn test_reflect_set_overwrites_existing() {
let mut t = JsObject::new();
t.set_property("k", JsValue::Smi(1)).unwrap();
assert!(reflect_set(&mut t, "k", JsValue::Smi(2)).unwrap());
assert_eq!(t.get_property("k"), JsValue::Smi(2));
}
#[test]
fn test_reflect_has_own_property() {
let mut t = JsObject::new();
t.set_property("p", JsValue::Null).unwrap();
assert!(reflect_has(&t, "p"));
assert!(!reflect_has(&t, "q"));
}
#[test]
fn test_reflect_has_prototype_chain() {
let proto = Rc::new(RefCell::new(JsObject::new()));
proto
.borrow_mut()
.set_property("up", JsValue::Boolean(true))
.unwrap();
let child = JsObject::with_prototype(proto);
assert!(reflect_has(&child, "up"));
}
#[test]
fn test_reflect_has_empty_object() {
let t = JsObject::new();
assert!(!reflect_has(&t, "anything"));
}
#[test]
fn test_reflect_delete_existing_property() {
let mut t = JsObject::new();
t.set_property("d", JsValue::Smi(1)).unwrap();
assert!(reflect_delete_property(&mut t, "d").unwrap());
assert!(!t.has_own_property("d"));
}
#[test]
fn test_reflect_delete_non_configurable_returns_false() {
let mut t = JsObject::new();
t.define_own_property("nc", JsValue::Smi(1), PropertyAttributes::WRITABLE)
.unwrap();
assert!(!reflect_delete_property(&mut t, "nc").unwrap());
}
#[test]
fn test_reflect_delete_missing_property_returns_true() {
let mut t = JsObject::new();
assert!(reflect_delete_property(&mut t, "ghost").unwrap());
}
#[test]
fn test_reflect_delete_configurable_property() {
let mut t = JsObject::new();
t.define_own_property(
"cfg",
JsValue::Smi(1),
PropertyAttributes::WRITABLE | PropertyAttributes::CONFIGURABLE,
)
.unwrap();
assert!(reflect_delete_property(&mut t, "cfg").unwrap());
assert!(!t.has_own_property("cfg"));
}
#[test]
fn test_reflect_define_property_creates_property() {
let mut t = JsObject::new();
assert!(
reflect_define_property(
&mut t,
"n",
JsValue::Smi(3),
PropertyAttributes::WRITABLE | PropertyAttributes::ENUMERABLE,
)
.unwrap()
);
assert_eq!(t.get_own_property("n"), Some(JsValue::Smi(3)));
}
#[test]
fn test_reflect_define_property_non_extensible_returns_false() {
let mut t = JsObject::new();
t.prevent_extensions();
assert!(
!reflect_define_property(&mut t, "new", JsValue::Smi(1), PropertyAttributes::WRITABLE,)
.unwrap()
);
}
#[test]
fn test_reflect_define_property_read_only() {
let mut t = JsObject::new();
assert!(
reflect_define_property(&mut t, "ro", JsValue::Smi(7), PropertyAttributes::empty(),)
.unwrap()
);
let desc = reflect_get_own_property_descriptor(&t, "ro");
assert!(desc.is_some());
let (_val, attrs) = desc.unwrap();
assert!(!attrs.contains(PropertyAttributes::WRITABLE));
}
#[test]
fn test_reflect_define_property_all_attributes() {
let mut t = JsObject::new();
let all = PropertyAttributes::WRITABLE
| PropertyAttributes::ENUMERABLE
| PropertyAttributes::CONFIGURABLE;
assert!(reflect_define_property(&mut t, "full", JsValue::Boolean(true), all).unwrap());
let (val, attrs) = reflect_get_own_property_descriptor(&t, "full").unwrap();
assert_eq!(val, JsValue::Boolean(true));
assert_eq!(attrs, all);
}
#[test]
fn test_reflect_get_own_property_descriptor_exists() {
let mut t = JsObject::new();
let attrs = PropertyAttributes::WRITABLE | PropertyAttributes::ENUMERABLE;
t.define_own_property("k", JsValue::Smi(5), attrs).unwrap();
let desc = reflect_get_own_property_descriptor(&t, "k");
assert!(desc.is_some());
let (val, a) = desc.unwrap();
assert_eq!(val, JsValue::Smi(5));
assert_eq!(a, attrs);
}
#[test]
fn test_reflect_get_own_property_descriptor_missing_returns_none() {
let t = JsObject::new();
assert!(reflect_get_own_property_descriptor(&t, "nope").is_none());
}
#[test]
fn test_reflect_get_own_property_descriptor_does_not_check_prototype() {
let proto = Rc::new(RefCell::new(JsObject::new()));
proto
.borrow_mut()
.set_property("inherited", JsValue::Smi(1))
.unwrap();
let child = JsObject::with_prototype(proto);
assert!(reflect_get_own_property_descriptor(&child, "inherited").is_none());
}
#[test]
fn test_reflect_get_prototype_of_none() {
let t = JsObject::new();
assert!(reflect_get_prototype_of(&t).is_none());
}
#[test]
fn test_reflect_get_prototype_of_some() {
let proto = Rc::new(RefCell::new(JsObject::new()));
let child = JsObject::with_prototype(Rc::clone(&proto));
let got = reflect_get_prototype_of(&child);
assert!(got.is_some());
assert!(Rc::ptr_eq(&proto, &got.unwrap()));
}
#[test]
fn test_reflect_set_prototype_of_success() {
let proto = Rc::new(RefCell::new(JsObject::new()));
let mut t = JsObject::new();
assert!(reflect_set_prototype_of(&mut t, Some(Rc::clone(&proto))));
assert!(t.prototype().is_some());
}
#[test]
fn test_reflect_set_prototype_of_null() {
let proto = Rc::new(RefCell::new(JsObject::new()));
let mut t = JsObject::with_prototype(proto);
assert!(reflect_set_prototype_of(&mut t, None));
assert!(t.prototype().is_none());
}
#[test]
fn test_reflect_set_prototype_of_non_extensible_same_is_ok() {
let proto = Rc::new(RefCell::new(JsObject::new()));
let mut t = JsObject::with_prototype(Rc::clone(&proto));
t.prevent_extensions();
assert!(reflect_set_prototype_of(&mut t, Some(Rc::clone(&proto))));
}
#[test]
fn test_reflect_set_prototype_of_non_extensible_different_returns_false() {
let mut t = JsObject::new();
t.prevent_extensions();
let new_proto = Rc::new(RefCell::new(JsObject::new()));
assert!(!reflect_set_prototype_of(&mut t, Some(new_proto)));
}
#[test]
fn test_reflect_is_extensible_new_object() {
let t = JsObject::new();
assert!(reflect_is_extensible(&t));
}
#[test]
fn test_reflect_is_extensible_after_prevent() {
let mut t = JsObject::new();
t.prevent_extensions();
assert!(!reflect_is_extensible(&t));
}
#[test]
fn test_reflect_prevent_extensions_returns_true() {
let mut t = JsObject::new();
assert!(reflect_prevent_extensions(&mut t));
assert!(!t.is_extensible());
}
#[test]
fn test_reflect_prevent_extensions_idempotent() {
let mut t = JsObject::new();
assert!(reflect_prevent_extensions(&mut t));
assert!(reflect_prevent_extensions(&mut t));
assert!(!t.is_extensible());
}
#[test]
fn test_reflect_own_keys_empty() {
let t = JsObject::new();
assert!(reflect_own_keys(&t).is_empty());
}
#[test]
fn test_reflect_own_keys_multiple() {
let mut t = JsObject::new();
t.set_property("a", JsValue::Smi(1)).unwrap();
t.set_property("b", JsValue::Smi(2)).unwrap();
t.set_property("c", JsValue::Smi(3)).unwrap();
let keys = reflect_own_keys(&t);
assert_eq!(keys.len(), 3);
}
#[test]
fn test_reflect_apply_with_this() {
let result = reflect_apply(
|this, _args| {
if this == JsValue::Smi(42) {
Ok(JsValue::Boolean(true))
} else {
Ok(JsValue::Boolean(false))
}
},
JsValue::Smi(42),
vec![],
)
.unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn test_reflect_apply_empty_args() {
let result = reflect_apply(
|_this, args| Ok(JsValue::Smi(args.len() as i32)),
JsValue::Undefined,
vec![],
)
.unwrap();
assert_eq!(result, JsValue::Smi(0));
}
#[test]
fn test_reflect_apply_multiple_args() {
let result = reflect_apply(
|_this, args| Ok(JsValue::Smi(args.len() as i32)),
JsValue::Undefined,
vec![JsValue::Smi(1), JsValue::Smi(2), JsValue::Smi(3)],
)
.unwrap();
assert_eq!(result, JsValue::Smi(3));
}
#[test]
fn test_reflect_construct_empty_args() {
let obj = reflect_construct(|_args| Ok(JsObject::new()), vec![]).unwrap();
assert!(reflect_own_keys(&obj).is_empty());
}
#[test]
fn test_reflect_construct_with_new_target_sets_prototype() {
let proto = Rc::new(RefCell::new(JsObject::new()));
proto
.borrow_mut()
.set_property("tag", JsValue::Boolean(true))
.unwrap();
let obj =
reflect_construct_with_new_target(|_args| Ok(JsObject::new()), vec![], Some(proto))
.unwrap();
assert!(obj.prototype().is_some());
}
#[test]
fn test_reflect_own_keys_empty_v2() {
let t = JsObject::new();
assert!(reflect_own_keys(&t).is_empty());
}
#[test]
fn test_reflect_own_keys_returns_all_own_keys() {
let mut t = JsObject::new();
t.set_property("a", JsValue::Smi(1)).unwrap();
t.set_property("b", JsValue::Smi(2)).unwrap();
let keys = reflect_own_keys(&t);
assert_eq!(keys.len(), 2);
assert!(keys.contains(&"a".to_string()));
assert!(keys.contains(&"b".to_string()));
}
#[test]
fn test_reflect_own_keys_does_not_include_prototype_keys() {
let proto = Rc::new(RefCell::new(JsObject::new()));
proto
.borrow_mut()
.set_property("inherited", JsValue::Smi(0))
.unwrap();
let mut child = JsObject::with_prototype(proto);
child.set_property("own", JsValue::Smi(1)).unwrap();
let keys = reflect_own_keys(&child);
assert!(keys.contains(&"own".to_string()));
assert!(!keys.contains(&"inherited".to_string()));
}
#[test]
fn test_reflect_apply_calls_target() {
let result = reflect_apply(
|_this, args| Ok(args.first().cloned().unwrap_or(JsValue::Undefined)),
JsValue::Undefined,
vec![JsValue::Smi(42)],
)
.unwrap();
assert_eq!(result, JsValue::Smi(42));
}
#[test]
fn test_reflect_apply_passes_this() {
let result = reflect_apply(|this, _args| Ok(this), JsValue::Boolean(true), vec![]).unwrap();
assert_eq!(result, JsValue::Boolean(true));
}
#[test]
fn test_reflect_construct_creates_object() {
let obj = reflect_construct(
|args| {
let mut o = JsObject::new();
if let Some(v) = args.first() {
o.set_property("val", v.clone()).unwrap();
}
Ok(o)
},
vec![JsValue::Smi(7)],
)
.unwrap();
assert_eq!(obj.get_property("val"), JsValue::Smi(7));
}
#[test]
fn test_reflect_own_keys_integer_sorted() {
let mut obj = JsObject::new();
obj.set_property("2", JsValue::Smi(1)).unwrap();
obj.set_property("0", JsValue::Smi(2)).unwrap();
obj.set_property("1", JsValue::Smi(3)).unwrap();
let keys = reflect_own_keys(&obj);
assert_eq!(keys, &["0", "1", "2"]);
}
#[test]
fn test_reflect_own_keys_integers_strings_order() {
let mut obj = JsObject::new();
obj.set_property("b", JsValue::Smi(1)).unwrap();
obj.set_property("5", JsValue::Smi(2)).unwrap();
obj.set_property("a", JsValue::Smi(3)).unwrap();
obj.set_property("0", JsValue::Smi(4)).unwrap();
let keys = reflect_own_keys(&obj);
assert_eq!(keys, &["0", "5", "b", "a"]);
}
#[test]
fn test_reflect_own_keys_includes_non_enumerable() {
let mut obj = JsObject::new();
obj.set_property("a", JsValue::Smi(1)).unwrap();
obj.define_own_property(
"b",
JsValue::Smi(2),
PropertyAttributes::WRITABLE | PropertyAttributes::CONFIGURABLE,
)
.unwrap();
let keys = reflect_own_keys(&obj);
assert!(keys.contains(&"a".to_string()));
assert!(keys.contains(&"b".to_string()));
}
#[test]
fn test_reflect_own_keys_empty_v3() {
let obj = JsObject::new();
assert!(reflect_own_keys(&obj).is_empty());
}
}