diff options
author | Matthew Olsson <matthewcolsson@gmail.com> | 2020-05-21 17:28:28 -0700 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-05-22 10:59:05 +0200 |
commit | c35732c0112abc41731609f09ef566f25bc6dcc3 (patch) | |
tree | 08673eb520ea66ff7852d551bd690da5ac8c798f | |
parent | 3a90a01dd405d1eb8ebad6e085586093d27c6357 (diff) | |
download | serenity-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.cpp | 33 | ||||
-rw-r--r-- | Libraries/LibJS/AST.h | 15 | ||||
-rw-r--r-- | Libraries/LibJS/Parser.cpp | 35 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/object-getter-setter-shorthand.js | 50 |
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); +} |