summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Olsson <matthewcolsson@gmail.com>2020-05-21 17:28:28 -0700
committerAndreas Kling <kling@serenityos.org>2020-05-22 10:59:05 +0200
commitc35732c0112abc41731609f09ef566f25bc6dcc3 (patch)
tree08673eb520ea66ff7852d551bd690da5ac8c798f
parent3a90a01dd405d1eb8ebad6e085586093d27c6357 (diff)
downloadserenity-c35732c0112abc41731609f09ef566f25bc6dcc3.zip
LibJS: Add object literal getter/setter shorthand
Adds support for the following syntax: let foo = { get x() { // ... }, set x(value) { // ... } }
-rw-r--r--Libraries/LibJS/AST.cpp33
-rw-r--r--Libraries/LibJS/AST.h15
-rw-r--r--Libraries/LibJS/Parser.cpp35
-rw-r--r--Libraries/LibJS/Tests/object-getter-setter-shorthand.js50
4 files changed, 117 insertions, 16 deletions
diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp
index 034a55ab8f..780af06b80 100644
--- a/Libraries/LibJS/AST.cpp
+++ b/Libraries/LibJS/AST.cpp
@@ -31,6 +31,7 @@
#include <AK/StringBuilder.h>
#include <LibJS/AST.h>
#include <LibJS/Interpreter.h>
+#include <LibJS/Runtime/Accessor.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/GlobalObject.h>
@@ -1129,7 +1130,7 @@ Value ObjectExpression::execute(Interpreter& interpreter) const
if (interpreter.exception())
return {};
- if (property.is_spread()) {
+ if (property.type() == ObjectProperty::Type::Spread) {
if (key_result.is_array()) {
auto& array_to_spread = static_cast<Array&>(key_result.as_object());
auto& elements = array_to_spread.elements();
@@ -1163,8 +1164,34 @@ Value ObjectExpression::execute(Interpreter& interpreter) const
auto value = property.value().execute(interpreter);
if (interpreter.exception())
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;
}
diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h
index aa81e3927c..7d68284889 100644
--- a/Libraries/LibJS/AST.h
+++ b/Libraries/LibJS/AST.h
@@ -722,17 +722,24 @@ private:
class ObjectProperty final : public ASTNode {
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_value(move(value))
+ , m_property_type(property_type)
{
}
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; }
+ Type type() const { return m_property_type; }
virtual void dump(int indent) const override;
virtual Value execute(Interpreter&) const override;
@@ -742,7 +749,7 @@ private:
NonnullRefPtr<Expression> m_key;
NonnullRefPtr<Expression> m_value;
- bool m_is_spread { false };
+ Type m_property_type;
};
class ObjectExpression : public Expression {
diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp
index 1c4c9951e2..e68e965cf1 100644
--- a/Libraries/LibJS/Parser.cpp
+++ b/Libraries/LibJS/Parser.cpp
@@ -482,14 +482,25 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
NonnullRefPtrVector<ObjectProperty> properties;
consume(TokenType::CurlyOpen);
+ auto property_type = ObjectProperty::Type::KeyValue;
+
while (!done() && !match(TokenType::CurlyClose)) {
RefPtr<Expression> property_key;
RefPtr<Expression> property_value;
auto need_colon = true;
- auto is_spread = false;
if (match_identifier_name()) {
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_value = create_ast_node<Identifier>(identifier);
need_colon = false;
@@ -506,23 +517,29 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
property_key = create_ast_node<SpreadExpression>(parse_expression(2));
property_value = property_key;
need_colon = false;
- is_spread = true;
+ property_type = ObjectProperty::Type::Spread;
} 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()));
- consume();
- continue;
+ if (property_type != ObjectProperty::Type::Getter && property_type != ObjectProperty::Type::Setter) {
+ 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()));
+ 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);
} else if (need_colon || match(TokenType::Colon)) {
consume(TokenType::Colon);
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);
- if (is_spread)
- property->set_is_spread();
+ property_type = ObjectProperty::Type::KeyValue;
if (!match(TokenType::Comma))
break;
diff --git a/Libraries/LibJS/Tests/object-getter-setter-shorthand.js b/Libraries/LibJS/Tests/object-getter-setter-shorthand.js
new file mode 100644
index 0000000000..e0ca209fa5
--- /dev/null
+++ b/Libraries/LibJS/Tests/object-getter-setter-shorthand.js
@@ -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);
+}