mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 02:48:11 +00:00
LibJS: Add spreading in object literals
Supports spreading strings, arrays, and other objects within object literals.
This commit is contained in:
parent
3cc31ff3f3
commit
104969a9f5
4 changed files with 116 additions and 0 deletions
|
@ -39,6 +39,7 @@
|
||||||
#include <LibJS/Runtime/PrimitiveString.h>
|
#include <LibJS/Runtime/PrimitiveString.h>
|
||||||
#include <LibJS/Runtime/Reference.h>
|
#include <LibJS/Runtime/Reference.h>
|
||||||
#include <LibJS/Runtime/ScriptFunction.h>
|
#include <LibJS/Runtime/ScriptFunction.h>
|
||||||
|
#include <LibJS/Runtime/Shape.h>
|
||||||
#include <LibJS/Runtime/StringObject.h>
|
#include <LibJS/Runtime/StringObject.h>
|
||||||
#include <LibJS/Runtime/Value.h>
|
#include <LibJS/Runtime/Value.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
@ -1028,6 +1029,35 @@ Value ObjectExpression::execute(Interpreter& interpreter) const
|
||||||
auto key_result = property.key().execute(interpreter);
|
auto key_result = property.key().execute(interpreter);
|
||||||
if (interpreter.exception())
|
if (interpreter.exception())
|
||||||
return {};
|
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 key = key_result.to_string();
|
||||||
auto value = property.value().execute(interpreter);
|
auto value = property.value().execute(interpreter);
|
||||||
if (interpreter.exception())
|
if (interpreter.exception())
|
||||||
|
|
|
@ -703,6 +703,9 @@ public:
|
||||||
const Expression& key() const { return m_key; }
|
const Expression& key() const { return m_key; }
|
||||||
const Expression& value() const { return m_value; }
|
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 void dump(int indent) const override;
|
||||||
virtual Value execute(Interpreter&) const override;
|
virtual Value execute(Interpreter&) const override;
|
||||||
|
|
||||||
|
@ -711,6 +714,7 @@ private:
|
||||||
|
|
||||||
NonnullRefPtr<Expression> m_key;
|
NonnullRefPtr<Expression> m_key;
|
||||||
NonnullRefPtr<Expression> m_value;
|
NonnullRefPtr<Expression> m_value;
|
||||||
|
bool m_is_spread { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
class ObjectExpression : public Expression {
|
class ObjectExpression : public Expression {
|
||||||
|
|
|
@ -446,6 +446,8 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
|
||||||
RefPtr<Expression> property_key;
|
RefPtr<Expression> property_key;
|
||||||
RefPtr<Expression> property_value;
|
RefPtr<Expression> property_value;
|
||||||
auto need_colon = true;
|
auto need_colon = true;
|
||||||
|
auto is_spread = false;
|
||||||
|
|
||||||
if (match_identifier_name()) {
|
if (match_identifier_name()) {
|
||||||
auto identifier = consume().value();
|
auto identifier = consume().value();
|
||||||
property_key = create_ast_node<StringLiteral>(identifier);
|
property_key = create_ast_node<StringLiteral>(identifier);
|
||||||
|
@ -459,6 +461,12 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
|
||||||
consume(TokenType::BracketOpen);
|
consume(TokenType::BracketOpen);
|
||||||
property_key = parse_expression(0);
|
property_key = parse_expression(0);
|
||||||
consume(TokenType::BracketClose);
|
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 {
|
} else {
|
||||||
m_parser_state.m_has_errors = true;
|
m_parser_state.m_has_errors = true;
|
||||||
auto& current_token = m_parser_state.m_current_token;
|
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);
|
auto property = create_ast_node<ObjectProperty>(*property_key, *property_value);
|
||||||
properties.append(property);
|
properties.append(property);
|
||||||
|
if (is_spread)
|
||||||
|
property->set_is_spread();
|
||||||
|
|
||||||
if (!match(TokenType::Comma))
|
if (!match(TokenType::Comma))
|
||||||
break;
|
break;
|
||||||
|
|
72
Libraries/LibJS/Tests/object-spread.js
Normal file
72
Libraries/LibJS/Tests/object-spread.js
Normal file
|
@ -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);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue