From 4d024f2838c70b8cfa63120a2485b882b7e8e7e6 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 28 Aug 2020 11:50:48 +0900 Subject: [PATCH 1/3] Support Holes in Array --- .../Sources/PrimaryTests/main.swift | 22 +++++++++-- IntegrationTests/bin/primary-tests.js | 1 + .../JavaScriptKit/BasicObjects/JSArray.swift | 39 +++++++++++++++++-- 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 22fc89fe6..7d041a631 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -86,22 +86,36 @@ try test("Array Iterator") { let globalObject1 = getJSValue(this: .global, name: "globalObject1") let globalObject1Ref = try expectObject(globalObject1) let prop_4 = getJSValue(this: globalObject1Ref, name: "prop_4") - let array = try expectArray(prop_4) + let array1 = try expectArray(prop_4) let expectedProp_4: [JSValue] = [ .number(3), .number(4), .string("str_elm_1"), .null, .undefined, .number(5), ] - try expectEqual(Array(array), expectedProp_4) + try expectEqual(Array(array1), expectedProp_4) + + // Ensure that iterator skips empty hole as JavaScript does. + let prop_8 = getJSValue(this: globalObject1Ref, name: "prop_8") + let array2 = try expectArray(prop_8) + let expectedProp_8: [JSValue] = [0, 2, 3, 6] + try expectEqual(Array(array2), expectedProp_8) } try test("Array RandomAccessCollection") { let globalObject1 = getJSValue(this: .global, name: "globalObject1") let globalObject1Ref = try expectObject(globalObject1) let prop_4 = getJSValue(this: globalObject1Ref, name: "prop_4") - let array = try expectArray(prop_4) + let array1 = try expectArray(prop_4) let expectedProp_4: [JSValue] = [ .number(3), .number(4), .string("str_elm_1"), .null, .undefined, .number(5), ] - try expectEqual([array[0], array[1], array[2], array[3], array[4], array[5]], expectedProp_4) + try expectEqual([array1[0], array1[1], array1[2], array1[3], array1[4], array1[5]], expectedProp_4) + + // Ensure that subscript can access empty hole + let prop_8 = getJSValue(this: globalObject1Ref, name: "prop_8") + let array2 = try expectArray(prop_8) + let expectedProp_8: [JSValue] = [ + 0, .undefined, 2, 3, .undefined, .undefined, 6 + ] + try expectEqual([array2[0], array2[1], array2[2], array2[3], array2[4], array2[5], array2[6]], expectedProp_8) } try test("Value Decoder") { diff --git a/IntegrationTests/bin/primary-tests.js b/IntegrationTests/bin/primary-tests.js index 9617178ca..9bd87c93d 100644 --- a/IntegrationTests/bin/primary-tests.js +++ b/IntegrationTests/bin/primary-tests.js @@ -23,6 +23,7 @@ global.globalObject1 = { } }, "prop_7": 3.14, + "prop_8": [0, , 2, 3, , , 6], } global.Animal = function(name, age, isCat) { diff --git a/Sources/JavaScriptKit/BasicObjects/JSArray.swift b/Sources/JavaScriptKit/BasicObjects/JSArray.swift index 85c811a39..3cfa5ba23 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSArray.swift @@ -28,11 +28,15 @@ extension JSArray: RandomAccessCollection { } public func next() -> Element? { - defer { index += 1 } - guard index < Int(ref.length.number!) else { + let currentIndex = index + guard currentIndex < Int(ref.length.number!) else { return nil } - let value = ref[index] + index += 1 + guard ref.hasOwnProperty!(currentIndex).boolean! else { + return next() + } + let value = ref[currentIndex] return value } } @@ -44,6 +48,35 @@ extension JSArray: RandomAccessCollection { public var startIndex: Int { 0 } public var endIndex: Int { ref.length.number.map(Int.init) ?? 0 } + + /// The number of elements in that array including empty hole. + /// Note that `length` respects JavaScript's `Array.prototype.length` + /// + /// e.g. + /// ```javascript + /// const array = [1, , 3]; + /// ``` + /// ```swift + /// let array: JSArray = ... + /// array.length // 3 + /// array.count // 2 + /// ``` + public var length: Int { + return Int(ref.length.number!) + } + + /// The number of elements in that array **not** including empty hole. + /// Note that `count` syncs with the number that `Iterator` can iterate. + /// See also: `JSArray.length` + public var count: Int { + return getObjectValuesLength(ref) + } +} + +private func getObjectValuesLength(_ object: JSObject) -> Int { + let objectClass = JSObject.global.Object.function! + let values = objectClass.values!(object).object! + return Int(values.length.number!) } extension JSValue { From 8649459909bbf3924d9c0fab6d9e9956b6b34ab5 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 29 Aug 2020 00:02:21 +0900 Subject: [PATCH 2/3] Use filter(() => true) --- Sources/JavaScriptKit/BasicObjects/JSArray.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSArray.swift b/Sources/JavaScriptKit/BasicObjects/JSArray.swift index 3cfa5ba23..f0663161e 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSArray.swift @@ -47,7 +47,7 @@ extension JSArray: RandomAccessCollection { public var startIndex: Int { 0 } - public var endIndex: Int { ref.length.number.map(Int.init) ?? 0 } + public var endIndex: Int { count } /// The number of elements in that array including empty hole. /// Note that `length` respects JavaScript's `Array.prototype.length` @@ -73,9 +73,9 @@ extension JSArray: RandomAccessCollection { } } +private let alwaysTrue = JSClosure { _ in .boolean(true) } private func getObjectValuesLength(_ object: JSObject) -> Int { - let objectClass = JSObject.global.Object.function! - let values = objectClass.values!(object).object! + let values = object.filter!(alwaysTrue).object! return Int(values.length.number!) } From be148cc029a7a7fac511baf678aea868fc021d27 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 29 Aug 2020 02:20:22 +0900 Subject: [PATCH 3/3] Revert accidently changed line --- Sources/JavaScriptKit/BasicObjects/JSArray.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSArray.swift b/Sources/JavaScriptKit/BasicObjects/JSArray.swift index f0663161e..4a8a6aecc 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSArray.swift @@ -47,7 +47,7 @@ extension JSArray: RandomAccessCollection { public var startIndex: Int { 0 } - public var endIndex: Int { count } + public var endIndex: Int { length } /// The number of elements in that array including empty hole. /// Note that `length` respects JavaScript's `Array.prototype.length`