diff options
-rw-r--r-- | Libraries/LibJS/AST.cpp | 30 | ||||
-rw-r--r-- | Libraries/LibJS/AST.h | 4 | ||||
-rw-r--r-- | Libraries/LibJS/Parser.cpp | 10 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/object-spread.js | 72 |
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 |