summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Libraries/LibJS/AST.cpp30
-rw-r--r--Libraries/LibJS/AST.h4
-rw-r--r--Libraries/LibJS/Parser.cpp10
-rw-r--r--Libraries/LibJS/Tests/object-spread.js72
4 files changed, 116 insertions, 0 deletions
diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp
index d2e4ff039b..40bb117080 100644
--- a/Libraries/LibJS/AST.cpp
+++ b/Libraries/LibJS/AST.cpp
@@ -39,6 +39,7 @@
#include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/Reference.h>
#include <LibJS/Runtime/ScriptFunction.h>
+#include <LibJS/Runtime/Shape.h>
#include <LibJS/Runtime/StringObject.h>
#include <LibJS/Runtime/Value.h>
#include <stdio.h>
@@ -1028,6 +1029,35 @@ Value ObjectExpression::execute(Interpreter& interpreter) const
auto key_result = property.key().execute(interpreter);
if (interpreter.exception())
return {};
+
+ if (property.is_spread()) {
+ if (key_result.is_array()) {
+ auto& array_to_spread = static_cast<Array&>(key_result.as_object());
+ auto& elements = array_to_spread.elements();
+
+ for (size_t i = 0; i < elements.size(); ++i) {
+ auto element = elements.at(i);
+ if (!element.is_empty())
+ object->put_by_index(i, element);
+ }
+ } else if (key_result.is_object()) {
+ auto& obj_to_spread = key_result.as_object();
+
+ for (auto& it : obj_to_spread.shape().property_table()) {
+ if (obj_to_spread.has_own_property(it.key) && it.value.attributes & Attribute::Enumerable)
+ object->put(it.key, obj_to_spread.get(it.key));
+ }
+ } else if (key_result.is_string()) {
+ auto& str_to_spread = key_result.as_string()->string();
+
+ for (size_t i = 0; i < str_to_spread.length(); i++) {
+ object->put_by_index(i, js_string(interpreter, str_to_spread.substring(i, 1)));
+ }
+ }
+
+ continue;
+ }
+
auto key = key_result.to_string();
auto value = property.value().execute(interpreter);
if (interpreter.exception())
diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h
index 395367afec..27d660f99e 100644
--- a/Libraries/LibJS/AST.h
+++ b/Libraries/LibJS/AST.h
@@ -703,6 +703,9 @@ public:
const Expression& key() const { return m_key; }
const Expression& value() const { return m_value; }
+ bool is_spread() const { return m_is_spread; }
+ void set_is_spread() { m_is_spread = true; }
+
virtual void dump(int indent) const override;
virtual Value execute(Interpreter&) const override;
@@ -711,6 +714,7 @@ private:
NonnullRefPtr<Expression> m_key;
NonnullRefPtr<Expression> m_value;
+ bool m_is_spread { false };
};
class ObjectExpression : public Expression {
diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp
index 63e0a5bd95..2288a31d92 100644
--- a/Libraries/LibJS/Parser.cpp
+++ b/Libraries/LibJS/Parser.cpp
@@ -446,6 +446,8 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
RefPtr<Expression> property_key;
RefPtr<Expression> property_value;
auto need_colon = true;
+ auto is_spread = false;
+
if (match_identifier_name()) {
auto identifier = consume().value();
property_key = create_ast_node<StringLiteral>(identifier);
@@ -459,6 +461,12 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
consume(TokenType::BracketOpen);
property_key = parse_expression(0);
consume(TokenType::BracketClose);
+ } else if (match(TokenType::TripleDot)) {
+ consume(TokenType::TripleDot);
+ property_key = create_ast_node<SpreadExpression>(parse_expression(0));
+ property_value = property_key;
+ need_colon = false;
+ is_spread = true;
} else {
m_parser_state.m_has_errors = true;
auto& current_token = m_parser_state.m_current_token;
@@ -476,6 +484,8 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
}
auto property = create_ast_node<ObjectProperty>(*property_key, *property_value);
properties.append(property);
+ if (is_spread)
+ property->set_is_spread();
if (!match(TokenType::Comma))
break;
diff --git a/Libraries/LibJS/Tests/object-spread.js b/Libraries/LibJS/Tests/object-spread.js
new file mode 100644
index 0000000000..18fab7abb8
--- /dev/null
+++ b/Libraries/LibJS/Tests/object-spread.js
@@ -0,0 +1,72 @@
+load("test-common.js");
+
+function testObjSpread(obj) {
+ return obj.foo === 0 &&
+ obj.bar === 1 &&
+ obj.baz === 2 &&
+ obj.qux === 3;
+}
+
+function testObjStrSpread(obj) {
+ return obj[0] === "a" &&
+ obj[1] === "b" &&
+ obj[2] === "c" &&
+ obj[3] === "d";
+}
+
+try {
+ let obj = {
+ foo: 0,
+ ...{ bar: 1, baz: 2 },
+ qux: 3,
+ };
+ assert(testObjSpread(obj));
+
+ obj = { foo: 0, bar: 1, baz: 2 };
+ obj.qux = 3;
+ assert(testObjSpread({ ...obj }));
+
+ let a = { bar: 1, baz: 2 };
+ obj = { foo: 0, ...a, qux: 3 };
+ assert(testObjSpread(obj));
+
+ obj = {
+ ...{},
+ ...{
+ ...{ foo: 0, bar: 1, baz: 2 },
+ },
+ qux: 3,
+ };
+ assert(testObjSpread(obj));
+
+ obj = { ..."abcd" };
+ assert(testObjStrSpread(obj));
+
+ obj = { ...["a", "b", "c", "d"] };
+ assert(testObjStrSpread(obj));
+
+ obj = { ...String("abcd") };
+ assert(testObjStrSpread(obj));
+
+ a = { foo: 0 };
+ Object.defineProperty(a, 'bar', {
+ value: 1,
+ enumerable: false,
+ });
+ obj = { ...a };
+ assert(obj.foo === 0 && obj.bar === undefined);
+
+ let empty = ({
+ ...undefined,
+ ...null,
+ ...1,
+ ...true,
+ ...function(){},
+ ...Date,
+ });
+ assert(Object.getOwnPropertyNames(empty).length === 0);
+
+ console.log("PASS");
+} catch (e) {
+ console.log("FAIL: " + e);
+} \ No newline at end of file