Bläddra i källkod

LibJS: Add object literal getter/setter shorthand

Adds support for the following syntax:

let foo = {
    get x() {
        // ...
    },
    set x(value) {
        // ...
    }
}
Matthew Olsson 5 år sedan
förälder
incheckning
c35732c011

+ 30 - 3
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;
 }

+ 11 - 4
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 {

+ 26 - 9
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;

+ 50 - 0
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);
+}