فهرست منبع

LibJS: Implement correct object property ordering

This commit introduces a way to get an object's own properties in the
correct order. The "correct order" for JS object properties is first all
array-like index properties (numeric keys) sorted by insertion order,
followed by all string properties sorted by insertion order.

Objects also now print correctly in the repl! Before this commit:

courage ~/js-tests $ js
> ({ foo: 1, bar: 2, baz: 3 })
{ bar: 2, foo: 1, baz: 3 }

After:

courage ~/js-tests $ js
> ({ foo: 1, bar: 2, baz: 3 })
{ foo: 1, bar: 2, baz: 3 }
mattco98 5 سال پیش
والد
کامیت
95abcc3722

+ 2 - 2
Libraries/LibJS/AST.cpp

@@ -1046,8 +1046,8 @@ Value ObjectExpression::execute(Interpreter& interpreter) const
             } 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)
+                for (auto& it : obj_to_spread.shape().property_table_ordered()) {
+                    if (it.value.attributes & Attribute::Enumerable)
                         object->put(it.key, obj_to_spread.get(it.key));
                 }
             } else if (key_result.is_string()) {

+ 5 - 0
Libraries/LibJS/Runtime/Cell.cpp

@@ -56,6 +56,11 @@ Interpreter& Cell::interpreter()
     return heap().interpreter();
 }
 
+Interpreter& Cell::interpreter() const
+{
+    return heap().interpreter();
+}
+
 const LogStream& operator<<(const LogStream& stream, const Cell* cell)
 {
     if (!cell)

+ 1 - 0
Libraries/LibJS/Runtime/Cell.h

@@ -60,6 +60,7 @@ public:
 
     Heap& heap() const;
     Interpreter& interpreter();
+    Interpreter& interpreter() const;
 
 protected:
     Cell() {}

+ 1 - 0
Libraries/LibJS/Runtime/Object.cpp

@@ -34,6 +34,7 @@
 #include <LibJS/Runtime/NativeProperty.h>
 #include <LibJS/Runtime/Object.h>
 #include <LibJS/Runtime/Shape.h>
+#include <LibJS/Runtime/StringObject.h>
 #include <LibJS/Runtime/Value.h>
 
 namespace JS {

+ 1 - 1
Libraries/LibJS/Runtime/ObjectConstructor.cpp

@@ -76,7 +76,7 @@ Value ObjectConstructor::get_own_property_names(Interpreter& interpreter)
             result->elements().append(js_string(interpreter, String::number(i)));
     }
 
-    for (auto& it : object->shape().property_table()) {
+    for (auto& it : object->shape().property_table_ordered()) {
         result->elements().append(js_string(interpreter, it.key));
     }
     return result;

+ 12 - 0
Libraries/LibJS/Runtime/Shape.cpp

@@ -117,6 +117,18 @@ size_t Shape::property_count() const
     return property_table().size();
 }
 
+Vector<Shape::Property> Shape::property_table_ordered() const
+{
+    auto vec = Vector<Shape::Property>();
+    vec.resize(property_table().size());
+
+    for (auto& it : property_table()) {
+        vec[it.value.offset] = { it.key, it.value };
+    }
+
+    return vec;
+}
+
 void Shape::ensure_property_table() const
 {
     if (m_property_table)

+ 7 - 0
Libraries/LibJS/Runtime/Shape.h

@@ -88,6 +88,13 @@ public:
     const HashMap<FlyString, PropertyMetadata>& property_table() const;
     size_t property_count() const;
 
+    struct Property {
+        FlyString key;
+        PropertyMetadata value;
+    };
+
+    Vector<Property> property_table_ordered() const;
+
     void set_prototype_without_transition(Object* new_prototype) { m_prototype = new_prototype; }
 
     void remove_property_from_unique_shape(const FlyString&, size_t offset);

+ 11 - 5
Libraries/LibJS/Tests/Object.getOwnPropertyNames.js

@@ -1,13 +1,19 @@
 load("test-common.js");
 
 try {
-    var names = Object.getOwnPropertyNames([1, 2, 3]);
+    let names = Object.getOwnPropertyNames([1, 2, 3]);
 
     assert(names.length === 4);
-    assert(names[0] === '0');
-    assert(names[1] === '1');
-    assert(names[2] === '2');
-    assert(names[3] === 'length');
+    assert(names[0] === "0");
+    assert(names[1] === "1");
+    assert(names[2] === "2");
+    assert(names[3] === "length");
+
+    names = Object.getOwnPropertyNames({ foo: 1, bar: 2, baz: 3 });
+    assert(names.length === 3);
+    assert(names[0] === "foo");
+    assert(names[1] === "bar");
+    assert(names[2] === "baz");
 
     console.log("PASS");
 } catch (e) {

+ 1 - 1
Userland/js.cpp

@@ -142,7 +142,7 @@ static void print_object(const JS::Object& object, HashTable<JS::Object*>& seen_
         fputs(", ", stdout);
 
     size_t index = 0;
-    for (auto& it : object.shape().property_table()) {
+    for (auto& it : object.shape().property_table_ordered()) {
         printf("\"\033[33;1m%s\033[0m\": ", it.key.characters());
         print_value(object.get_direct(it.value.offset), seen_objects);
         if (index != object.shape().property_count() - 1)