diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index 62f2b3240e..816b78f6bd 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -118,6 +118,21 @@ fn object_field_set() { assert_eq!(&exec(scenario), "22"); } +#[test] +fn object_spread() { + let scenario = r#" + var b = {x: -1, z: -3} + var a = {x: 1, y: 2, ...b}; + "#; + + check_output(&[ + TestAction::Execute(scenario), + TestAction::TestEq("a.x", "-1"), + TestAction::TestEq("a.y", "2"), + TestAction::TestEq("a.z", "-3"), + ]); +} + #[test] fn spread_with_arguments() { let scenario = r#" diff --git a/boa/src/syntax/ast/node/object/mod.rs b/boa/src/syntax/ast/node/object/mod.rs index 39d27503e1..146d644104 100644 --- a/boa/src/syntax/ast/node/object/mod.rs +++ b/boa/src/syntax/ast/node/object/mod.rs @@ -161,7 +161,21 @@ impl Executable for Object { ) } }, - _ => {} //unimplemented!("{:?} type of property", i), + // [spec]: https://tc39.es/ecma262/#sec-runtime-semantics-propertydefinitionevaluation + PropertyDefinition::SpreadObject(node) => { + let val = node.run(context)?; + + if val.is_null_or_undefined() { + continue; + } + + obj.as_object().unwrap().copy_data_properties::( + &val, + vec![], + context, + )?; + } + _ => {} // unimplemented!("{:?} type of property", i), } } diff --git a/boa/src/syntax/ast/node/object/tests.rs b/boa/src/syntax/ast/node/object/tests.rs index 64b4594ba9..4d49abac81 100644 --- a/boa/src/syntax/ast/node/object/tests.rs +++ b/boa/src/syntax/ast/node/object/tests.rs @@ -1,3 +1,78 @@ +use crate::exec; + +#[test] +fn spread_shallow_clone() { + let scenario = r#" + var a = { x: {} }; + var aClone = { ...a }; + + a.x === aClone.x + "#; + assert_eq!(&exec(scenario), "true"); +} + +#[test] +fn spread_merge() { + let scenario = r#" + var a = { x: 1, y: 2 }; + var b = { x: -1, z: -3, ...a }; + + (b.x === 1) && (b.y === 2) && (b.z === -3) + "#; + assert_eq!(&exec(scenario), "true"); +} + +#[test] +fn spread_overriding_properties() { + let scenario = r#" + var a = { x: 0, y: 0 }; + var aWithOverrides = { ...a, ...{ x: 1, y: 2 } }; + + (aWithOverrides.x === 1) && (aWithOverrides.y === 2) + "#; + assert_eq!(&exec(scenario), "true"); +} + +#[test] +fn spread_getters_in_initializer() { + let scenario = r#" + var a = { x: 42 }; + var aWithXGetter = { ...a, get x() { throw new Error('not thrown yet') } }; + "#; + assert_eq!(&exec(scenario), "undefined"); +} + +#[test] +fn spread_getters_in_object() { + let scenario = r#" + var a = { x: 42 }; + var aWithXGetter = { ...a, ... { get x() { throw new Error('not thrown yet') } } }; + "#; + assert_eq!(&exec(scenario), "\"Error\": \"not thrown yet\""); +} + +#[test] +fn spread_setters() { + let scenario = r#" + var z = { set x(nexX) { throw new Error() }, ... { x: 1 } }; + "#; + assert_eq!(&exec(scenario), "undefined"); +} + +#[test] +fn spread_null_and_undefined_ignored() { + let scenario = r#" + var a = { ...null, ...undefined }; + var count = 0; + + for (key in a) { count++; } + + count === 0 + "#; + + assert_eq!(&exec(scenario), "true"); +} + #[test] fn fmt() { super::super::test_formatting( diff --git a/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs b/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs index eeae4ea58a..29ca8c17b5 100644 --- a/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs +++ b/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs @@ -273,3 +273,24 @@ fn check_object_shorthand_multiple_properties() { ], ); } + +#[test] +fn check_object_spread() { + let object_properties = vec![ + PropertyDefinition::property("a", Const::from(1)), + PropertyDefinition::spread_object(Identifier::from("b")), + ]; + + check_parser( + "const x = { a: 1, ...b }; + ", + vec![DeclarationList::Const( + vec![Declaration::new_with_identifier( + "x", + Some(Object::from(object_properties).into()), + )] + .into(), + ) + .into()], + ); +} diff --git a/boa/src/syntax/parser/tests.rs b/boa/src/syntax/parser/tests.rs index b571e06318..12fae2072e 100644 --- a/boa/src/syntax/parser/tests.rs +++ b/boa/src/syntax/parser/tests.rs @@ -4,7 +4,8 @@ use super::Parser; use crate::syntax::ast::{ node::{ field::GetConstField, ArrowFunctionDecl, Assign, BinOp, Call, Declaration, DeclarationList, - FormalParameter, FunctionDecl, Identifier, If, New, Node, Return, StatementList, UnaryOp, + FormalParameter, FunctionDecl, Identifier, If, New, Node, Object, PropertyDefinition, + Return, StatementList, UnaryOp, }, op::{self, CompOp, LogOp, NumOp}, Const, @@ -299,6 +300,33 @@ fn increment_in_comma_op() { ); } +#[test] +fn spread_in_object() { + let s = r#" + let x = { + a: 1, + ...b, + } + "#; + + let object_properties = vec![ + PropertyDefinition::property("a", Const::from(1)), + PropertyDefinition::spread_object(Identifier::from("b")), + ]; + + check_parser( + s, + vec![DeclarationList::Let( + vec![Declaration::new_with_identifier::<&str, Option>( + "x", + Some(Object::from(object_properties).into()), + )] + .into(), + ) + .into()], + ); +} + #[test] fn spread_in_arrow_function() { let s = r#"