mirror of
https://github.com/RGBCube/serenity
synced 2025-07-03 05:52:13 +00:00
LibJS: Add object literal getter/setter shorthand
Adds support for the following syntax: let foo = { get x() { // ... }, set x(value) { // ... } }
This commit is contained in:
parent
3a90a01dd4
commit
c35732c011
4 changed files with 117 additions and 16 deletions
|
@ -31,6 +31,7 @@
|
||||||
#include <AK/StringBuilder.h>
|
#include <AK/StringBuilder.h>
|
||||||
#include <LibJS/AST.h>
|
#include <LibJS/AST.h>
|
||||||
#include <LibJS/Interpreter.h>
|
#include <LibJS/Interpreter.h>
|
||||||
|
#include <LibJS/Runtime/Accessor.h>
|
||||||
#include <LibJS/Runtime/Array.h>
|
#include <LibJS/Runtime/Array.h>
|
||||||
#include <LibJS/Runtime/Error.h>
|
#include <LibJS/Runtime/Error.h>
|
||||||
#include <LibJS/Runtime/GlobalObject.h>
|
#include <LibJS/Runtime/GlobalObject.h>
|
||||||
|
@ -1129,7 +1130,7 @@ Value ObjectExpression::execute(Interpreter& interpreter) const
|
||||||
if (interpreter.exception())
|
if (interpreter.exception())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
if (property.is_spread()) {
|
if (property.type() == ObjectProperty::Type::Spread) {
|
||||||
if (key_result.is_array()) {
|
if (key_result.is_array()) {
|
||||||
auto& array_to_spread = static_cast<Array&>(key_result.as_object());
|
auto& array_to_spread = static_cast<Array&>(key_result.as_object());
|
||||||
auto& elements = array_to_spread.elements();
|
auto& elements = array_to_spread.elements();
|
||||||
|
@ -1163,8 +1164,34 @@ Value ObjectExpression::execute(Interpreter& interpreter) const
|
||||||
auto value = property.value().execute(interpreter);
|
auto value = property.value().execute(interpreter);
|
||||||
if (interpreter.exception())
|
if (interpreter.exception())
|
||||||
return {};
|
return {};
|
||||||
update_function_name(value, key);
|
|
||||||
object->put(key, value);
|
String name = key;
|
||||||
|
if (property.type() == ObjectProperty::Type::Getter) {
|
||||||
|
name = String::format("get %s", key.characters());
|
||||||
|
} else if (property.type() == ObjectProperty::Type::Setter) {
|
||||||
|
name = String::format("set %s", key.characters());
|
||||||
|
}
|
||||||
|
|
||||||
|
update_function_name(value, name);
|
||||||
|
|
||||||
|
if (property.type() == ObjectProperty::Type::Getter || property.type() == ObjectProperty::Type::Setter) {
|
||||||
|
Value getter;
|
||||||
|
Value setter;
|
||||||
|
auto existing_property_metadata = object->shape().lookup(key);
|
||||||
|
Value existing_property;
|
||||||
|
if (existing_property_metadata.has_value())
|
||||||
|
existing_property = object->get_direct(existing_property_metadata.value().offset);
|
||||||
|
if (property.type() == ObjectProperty::Type::Getter) {
|
||||||
|
getter = value;
|
||||||
|
setter = existing_property.is_accessor() ? existing_property.as_accessor().setter() : Value();
|
||||||
|
} else {
|
||||||
|
getter = existing_property.is_accessor() ? existing_property.as_accessor().getter() : Value();
|
||||||
|
setter = value;
|
||||||
|
}
|
||||||
|
object->put_own_property(*object, key, Attribute::Configurable | Attribute::Enumerable, Accessor::create(interpreter, getter, setter), Object::PutOwnPropertyMode::DefineProperty);
|
||||||
|
} else {
|
||||||
|
object->put(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
|
@ -722,17 +722,24 @@ private:
|
||||||
|
|
||||||
class ObjectProperty final : public ASTNode {
|
class ObjectProperty final : public ASTNode {
|
||||||
public:
|
public:
|
||||||
ObjectProperty(NonnullRefPtr<Expression> key, NonnullRefPtr<Expression> value)
|
enum class Type {
|
||||||
|
KeyValue,
|
||||||
|
Getter,
|
||||||
|
Setter,
|
||||||
|
Spread,
|
||||||
|
};
|
||||||
|
|
||||||
|
ObjectProperty(NonnullRefPtr<Expression> key, NonnullRefPtr<Expression> value, Type property_type)
|
||||||
: m_key(move(key))
|
: m_key(move(key))
|
||||||
, m_value(move(value))
|
, m_value(move(value))
|
||||||
|
, m_property_type(property_type)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
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; }
|
Type type() const { return m_property_type; }
|
||||||
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;
|
||||||
|
@ -742,7 +749,7 @@ private:
|
||||||
|
|
||||||
NonnullRefPtr<Expression> m_key;
|
NonnullRefPtr<Expression> m_key;
|
||||||
NonnullRefPtr<Expression> m_value;
|
NonnullRefPtr<Expression> m_value;
|
||||||
bool m_is_spread { false };
|
Type m_property_type;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ObjectExpression : public Expression {
|
class ObjectExpression : public Expression {
|
||||||
|
|
|
@ -482,14 +482,25 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
|
||||||
NonnullRefPtrVector<ObjectProperty> properties;
|
NonnullRefPtrVector<ObjectProperty> properties;
|
||||||
consume(TokenType::CurlyOpen);
|
consume(TokenType::CurlyOpen);
|
||||||
|
|
||||||
|
auto property_type = ObjectProperty::Type::KeyValue;
|
||||||
|
|
||||||
while (!done() && !match(TokenType::CurlyClose)) {
|
while (!done() && !match(TokenType::CurlyClose)) {
|
||||||
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();
|
||||||
|
if (property_type == ObjectProperty::Type::KeyValue) {
|
||||||
|
if (identifier == "get" && !match(TokenType::ParenOpen)) {
|
||||||
|
property_type = ObjectProperty::Type::Getter;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (identifier == "set" && !match(TokenType::ParenOpen)) {
|
||||||
|
property_type = ObjectProperty::Type::Setter;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
property_key = create_ast_node<StringLiteral>(identifier);
|
property_key = create_ast_node<StringLiteral>(identifier);
|
||||||
property_value = create_ast_node<Identifier>(identifier);
|
property_value = create_ast_node<Identifier>(identifier);
|
||||||
need_colon = false;
|
need_colon = false;
|
||||||
|
@ -506,23 +517,29 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
|
||||||
property_key = create_ast_node<SpreadExpression>(parse_expression(2));
|
property_key = create_ast_node<SpreadExpression>(parse_expression(2));
|
||||||
property_value = property_key;
|
property_value = property_key;
|
||||||
need_colon = false;
|
need_colon = false;
|
||||||
is_spread = true;
|
property_type = ObjectProperty::Type::Spread;
|
||||||
} else {
|
} else {
|
||||||
syntax_error(String::format("Unexpected token %s as member in object initialization. Expected a numeric literal, string literal or identifier", m_parser_state.m_current_token.name()));
|
if (property_type != ObjectProperty::Type::Getter && property_type != ObjectProperty::Type::Setter) {
|
||||||
consume();
|
syntax_error(String::format("Unexpected token %s as member in object initialization. Expected a numeric literal, string literal or identifier", m_parser_state.m_current_token.name()));
|
||||||
continue;
|
consume();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto name = property_type == ObjectProperty::Type::Getter ? "get" : "set";
|
||||||
|
property_key = create_ast_node<StringLiteral>(name);
|
||||||
|
property_value = create_ast_node<Identifier>(name);
|
||||||
|
need_colon = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_spread && match(TokenType::ParenOpen)) {
|
if (property_type != ObjectProperty::Type::Spread && match(TokenType::ParenOpen)) {
|
||||||
property_value = parse_function_node<FunctionExpression>(false);
|
property_value = parse_function_node<FunctionExpression>(false);
|
||||||
} else if (need_colon || match(TokenType::Colon)) {
|
} else if (need_colon || match(TokenType::Colon)) {
|
||||||
consume(TokenType::Colon);
|
consume(TokenType::Colon);
|
||||||
property_value = parse_expression(2);
|
property_value = parse_expression(2);
|
||||||
}
|
}
|
||||||
auto property = create_ast_node<ObjectProperty>(*property_key, *property_value);
|
auto property = create_ast_node<ObjectProperty>(*property_key, *property_value, property_type);
|
||||||
properties.append(property);
|
properties.append(property);
|
||||||
if (is_spread)
|
property_type = ObjectProperty::Type::KeyValue;
|
||||||
property->set_is_spread();
|
|
||||||
|
|
||||||
if (!match(TokenType::Comma))
|
if (!match(TokenType::Comma))
|
||||||
break;
|
break;
|
||||||
|
|
50
Libraries/LibJS/Tests/object-getter-setter-shorthand.js
Normal file
50
Libraries/LibJS/Tests/object-getter-setter-shorthand.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
load("test-common.js")
|
||||||
|
|
||||||
|
try {
|
||||||
|
let o = {
|
||||||
|
get() { return 5; },
|
||||||
|
set() { return 10; },
|
||||||
|
};
|
||||||
|
assert(o.get() === 5);
|
||||||
|
assert(o.set() === 10);
|
||||||
|
|
||||||
|
o = {
|
||||||
|
get x() { return 5; },
|
||||||
|
set x(_) { },
|
||||||
|
};
|
||||||
|
assert(o.x === 5);
|
||||||
|
o.x = 10;
|
||||||
|
assert(o.x === 5);
|
||||||
|
|
||||||
|
o = {
|
||||||
|
get x() {
|
||||||
|
return this._x + 1;
|
||||||
|
},
|
||||||
|
set x(value) {
|
||||||
|
this._x = value + 1;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
assert(isNaN(o.x));
|
||||||
|
o.x = 10;
|
||||||
|
assert(o.x === 12);
|
||||||
|
o.x = 20;
|
||||||
|
assert(o.x === 22);
|
||||||
|
|
||||||
|
o = {
|
||||||
|
get x() { return 5; },
|
||||||
|
get x() { return 10; },
|
||||||
|
};
|
||||||
|
|
||||||
|
assert(o.x === 10);
|
||||||
|
|
||||||
|
o = {
|
||||||
|
set x(value) { return 10; },
|
||||||
|
};
|
||||||
|
|
||||||
|
assert((o.x = 20) === 20);
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue