From a1ec2599a63f9669658c4cd37ecdca463de390e5 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 27 Aug 2020 21:26:00 +0900 Subject: [PATCH 01/11] Fix invalid array termination for null and undefined (#38) * Add test case to check invalid terminator * Fix invalid array termination --- IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift | 2 +- IntegrationTests/bin/primary-tests.js | 4 ++-- Sources/JavaScriptKit/JSArrayRef.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index d2dffa26a..3c3e5deca 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -88,7 +88,7 @@ test("Array Iterator") { let prop_4 = getJSValue(this: globalObject1Ref, name: "prop_4") let array = try expectArray(prop_4) let expectedProp_4: [JSValue] = [ - .number(3), .number(4), .string("str_elm_1"), .number(5), + .number(3), .number(4), .string("str_elm_1"), .null, .undefined, .number(5), ] try expectEqual(Array(array), expectedProp_4) } diff --git a/IntegrationTests/bin/primary-tests.js b/IntegrationTests/bin/primary-tests.js index 3b4eda72a..094ee4854 100644 --- a/IntegrationTests/bin/primary-tests.js +++ b/IntegrationTests/bin/primary-tests.js @@ -5,7 +5,7 @@ global.globalObject1 = { "prop_2": 2, "prop_3": true, "prop_4": [ - 3, 4, "str_elm_1", 5, + 3, 4, "str_elm_1", null, undefined, 5, ], "prop_5": { "func1": function () { return }, @@ -41,4 +41,4 @@ const { startWasiTask } = require("../lib") startWasiTask("./dist/PrimaryTests.wasm").catch(err => { console.log(err) -}); \ No newline at end of file +}); diff --git a/Sources/JavaScriptKit/JSArrayRef.swift b/Sources/JavaScriptKit/JSArrayRef.swift index a84fb27d2..df3937b12 100644 --- a/Sources/JavaScriptKit/JSArrayRef.swift +++ b/Sources/JavaScriptKit/JSArrayRef.swift @@ -34,7 +34,7 @@ extension JSArrayRef: RandomAccessCollection { return nil } let value = ref[index] - return value.isNull ? nil : value + return value } } From f3aad66f4c8f14d655d8ee9b2f548edee82dbc97 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 28 Aug 2020 10:15:57 +0900 Subject: [PATCH 02/11] Refine public API (#40) * Refine public API Drop Ref suffix * Drop ref suffix from JSFunctionRef --- .../Sources/PrimaryTests/UnitTestUtils.swift | 4 ++-- .../Sources/PrimaryTests/main.swift | 2 +- README.md | 6 ++--- .../JSArray.swift} | 23 +++++++++++-------- Sources/JavaScriptKit/Deprecated.swift | 8 +++++++ .../{ => FundamentalObjects}/JSFunction.swift | 18 +++++++-------- .../{ => FundamentalObjects}/JSObject.swift | 8 +++---- Sources/JavaScriptKit/JSValue.swift | 20 +++++++--------- .../JavaScriptKit/JSValueConvertible.swift | 14 +++++------ Sources/JavaScriptKit/JSValueDecoder.swift | 16 ++++++------- 10 files changed, 64 insertions(+), 55 deletions(-) rename Sources/JavaScriptKit/{JSArrayRef.swift => BasicObjects/JSArray.swift} (67%) create mode 100644 Sources/JavaScriptKit/Deprecated.swift rename Sources/JavaScriptKit/{ => FundamentalObjects}/JSFunction.swift (90%) rename Sources/JavaScriptKit/{ => FundamentalObjects}/JSObject.swift (82%) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift index f4cc69663..becf261ba 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift @@ -37,7 +37,7 @@ func expectEqual( } } -func expectObject(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSObjectRef { +func expectObject(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSObject { switch value { case let .object(ref): return ref default: @@ -45,7 +45,7 @@ func expectObject(_ value: JSValue, file: StaticString = #file, line: UInt = #li } } -func expectArray(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSArrayRef { +func expectArray(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSArray { guard let array = value.array else { throw MessageError("Type of \(value) should be \"object\"", file: file, line: line, column: column) } diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 3c3e5deca..16a78d4bc 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -1,7 +1,7 @@ import JavaScriptKit test("Literal Conversion") { - let global = JSObjectRef.global + let global = JSObject.global let inputs: [JSValue] = [ .boolean(true), .boolean(false), diff --git a/README.md b/README.md index 6ef24ef3d..155d517eb 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,8 @@ Can be written in Swift using JavaScriptKit ```swift import JavaScriptKit -let alert = JSObjectRef.global.alert.function! -let document = JSObjectRef.global.document.object! +let alert = JSObject.global.alert.function! +let document = JSObject.global.document.object! let divElement = document.createElement!("div").object! divElement.innerText = "Hello, world" @@ -64,7 +64,7 @@ struct Pet: Codable { let owner: Owner } -let jsPet = JSObjectRef.global.pet +let jsPet = JSObject.global.pet let swiftPet: Pet = try JSValueDecoder().decode(from: jsPet) alert("Swift is running on browser!") diff --git a/Sources/JavaScriptKit/JSArrayRef.swift b/Sources/JavaScriptKit/BasicObjects/JSArray.swift similarity index 67% rename from Sources/JavaScriptKit/JSArrayRef.swift rename to Sources/JavaScriptKit/BasicObjects/JSArray.swift index df3937b12..85c811a39 100644 --- a/Sources/JavaScriptKit/JSArrayRef.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSArray.swift @@ -1,20 +1,19 @@ +public class JSArray { + static let classObject = JSObject.global.Array.function! -public class JSArrayRef { - static let classObject = JSObjectRef.global.Array.function! - - static func isArray(_ object: JSObjectRef) -> Bool { + static func isArray(_ object: JSObject) -> Bool { classObject.isArray!(object).boolean! } - let ref: JSObjectRef + let ref: JSObject - public init?(_ ref: JSObjectRef) { + public init?(_ ref: JSObject) { guard Self.isArray(ref) else { return nil } self.ref = ref } } -extension JSArrayRef: RandomAccessCollection { +extension JSArray: RandomAccessCollection { public typealias Element = JSValue public func makeIterator() -> Iterator { @@ -22,9 +21,9 @@ extension JSArrayRef: RandomAccessCollection { } public class Iterator: IteratorProtocol { - let ref: JSObjectRef + let ref: JSObject var index = 0 - init(ref: JSObjectRef) { + init(ref: JSObject) { self.ref = ref } @@ -46,3 +45,9 @@ extension JSArrayRef: RandomAccessCollection { public var endIndex: Int { ref.length.number.map(Int.init) ?? 0 } } + +extension JSValue { + public var array: JSArray? { + object.flatMap(JSArray.init) + } +} diff --git a/Sources/JavaScriptKit/Deprecated.swift b/Sources/JavaScriptKit/Deprecated.swift new file mode 100644 index 000000000..69306ac5f --- /dev/null +++ b/Sources/JavaScriptKit/Deprecated.swift @@ -0,0 +1,8 @@ +@available(*, deprecated, renamed: "JSObject") +public typealias JSObjectRef = JSObject + +@available(*, deprecated, renamed: "JSArray") +public typealias JSArrayRef = JSArray + +@available(*, deprecated, renamed: "JSFunction") +public typealias JSFunctionRef = JSFunction diff --git a/Sources/JavaScriptKit/JSFunction.swift b/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift similarity index 90% rename from Sources/JavaScriptKit/JSFunction.swift rename to Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift index f3b0c5b22..8379b175f 100644 --- a/Sources/JavaScriptKit/JSFunction.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift @@ -1,8 +1,8 @@ import _CJavaScriptKit -public class JSFunctionRef: JSObjectRef { +public class JSFunction: JSObject { @discardableResult - public func callAsFunction(this: JSObjectRef? = nil, arguments: [JSValueConvertible]) -> JSValue { + public func callAsFunction(this: JSObject? = nil, arguments: [JSValueConvertible]) -> JSValue { let result = arguments.withRawJSValues { rawValues in rawValues.withUnsafeBufferPointer { bufferPointer -> RawJSValue in let argv = bufferPointer.baseAddress @@ -25,11 +25,11 @@ public class JSFunctionRef: JSObjectRef { } @discardableResult - public func callAsFunction(this: JSObjectRef? = nil, _ arguments: JSValueConvertible...) -> JSValue { + public func callAsFunction(this: JSObject? = nil, _ arguments: JSValueConvertible...) -> JSValue { self(this: this, arguments: arguments) } - public func new(_ arguments: JSValueConvertible...) -> JSObjectRef { + public func new(_ arguments: JSValueConvertible...) -> JSObject { new(arguments: arguments) } @@ -37,7 +37,7 @@ public class JSFunctionRef: JSObjectRef { // a) the constructor explicitly returns an object, or // b) the constructor returns nothing, which causes JS to return the `this` value, or // c) the constructor returns undefined, null or a non-object, in which case JS also returns `this`. - public func new(arguments: [JSValueConvertible]) -> JSObjectRef { + public func new(arguments: [JSValueConvertible]) -> JSObject { arguments.withRawJSValues { rawValues in rawValues.withUnsafeBufferPointer { bufferPointer in let argv = bufferPointer.baseAddress @@ -47,13 +47,13 @@ public class JSFunctionRef: JSObjectRef { self.id, argv, Int32(argc), &resultObj ) - return JSObjectRef(id: resultObj) + return JSObject(id: resultObj) } } } @available(*, unavailable, message: "Please use JSClosure instead") - public static func from(_: @escaping ([JSValue]) -> JSValue) -> JSFunctionRef { + public static func from(_: @escaping ([JSValue]) -> JSValue) -> JSFunction { fatalError("unavailable") } @@ -62,7 +62,7 @@ public class JSFunctionRef: JSObjectRef { } } -public class JSClosure: JSFunctionRef { +public class JSClosure: JSFunction { static var sharedFunctions: [JavaScriptHostFuncRef: ([JSValue]) -> JSValue] = [:] private var hostFuncRef: JavaScriptHostFuncRef = 0 @@ -128,6 +128,6 @@ public func _call_host_function( $0.jsValue() } let result = hostFunc(arguments) - let callbackFuncRef = JSFunctionRef(id: callbackFuncRef) + let callbackFuncRef = JSFunction(id: callbackFuncRef) _ = callbackFuncRef(result) } diff --git a/Sources/JavaScriptKit/JSObject.swift b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift similarity index 82% rename from Sources/JavaScriptKit/JSObject.swift rename to Sources/JavaScriptKit/FundamentalObjects/JSObject.swift index 4a6f47f9f..5955aa536 100644 --- a/Sources/JavaScriptKit/JSObject.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift @@ -1,7 +1,7 @@ import _CJavaScriptKit @dynamicMemberLookup -public class JSObjectRef: Equatable { +public class JSObject: Equatable { internal var id: UInt32 init(id: UInt32) { self.id = id @@ -30,16 +30,16 @@ public class JSObjectRef: Equatable { set { setJSValue(this: self, index: Int32(index), value: newValue) } } - public func isInstanceOf(_ constructor: JSFunctionRef) -> Bool { + public func isInstanceOf(_ constructor: JSFunction) -> Bool { _instanceof(id, constructor.id) } static let _JS_Predef_Value_Global: UInt32 = 0 - public static let global = JSObjectRef(id: _JS_Predef_Value_Global) + public static let global = JSObject(id: _JS_Predef_Value_Global) deinit { _destroy_ref(id) } - public static func == (lhs: JSObjectRef, rhs: JSObjectRef) -> Bool { + public static func == (lhs: JSObject, rhs: JSObject) -> Bool { return lhs.id == rhs.id } diff --git a/Sources/JavaScriptKit/JSValue.swift b/Sources/JavaScriptKit/JSValue.swift index 14434130f..4ac7a4579 100644 --- a/Sources/JavaScriptKit/JSValue.swift +++ b/Sources/JavaScriptKit/JSValue.swift @@ -4,10 +4,10 @@ public enum JSValue: Equatable { case boolean(Bool) case string(String) case number(Double) - case object(JSObjectRef) + case object(JSObject) case null case undefined - case function(JSFunctionRef) + case function(JSFunction) public var boolean: Bool? { switch self { @@ -30,20 +30,16 @@ public enum JSValue: Equatable { } } - public var object: JSObjectRef? { + public var object: JSObject? { switch self { case let .object(object): return object default: return nil } } - public var array: JSArrayRef? { - object.flatMap { JSArrayRef($0) } - } - public var isNull: Bool { return self == .null } public var isUndefined: Bool { return self == .undefined } - public var function: JSFunctionRef? { + public var function: JSFunction? { switch self { case let .function(function): return function default: return nil @@ -69,7 +65,7 @@ extension JSValue: ExpressibleByIntegerLiteral { } } -public func getJSValue(this: JSObjectRef, name: String) -> JSValue { +public func getJSValue(this: JSObject, name: String) -> JSValue { var rawValue = RawJSValue() _get_prop(this.id, name, Int32(name.count), &rawValue.kind, @@ -77,13 +73,13 @@ public func getJSValue(this: JSObjectRef, name: String) -> JSValue { return rawValue.jsValue() } -public func setJSValue(this: JSObjectRef, name: String, value: JSValue) { +public func setJSValue(this: JSObject, name: String, value: JSValue) { value.withRawJSValue { rawValue in _set_prop(this.id, name, Int32(name.count), rawValue.kind, rawValue.payload1, rawValue.payload2, rawValue.payload3) } } -public func getJSValue(this: JSObjectRef, index: Int32) -> JSValue { +public func getJSValue(this: JSObject, index: Int32) -> JSValue { var rawValue = RawJSValue() _get_subscript(this.id, index, &rawValue.kind, @@ -91,7 +87,7 @@ public func getJSValue(this: JSObjectRef, index: Int32) -> JSValue { return rawValue.jsValue() } -public func setJSValue(this: JSObjectRef, index: Int32, value: JSValue) { +public func setJSValue(this: JSObject, index: Int32, value: JSValue) { value.withRawJSValue { rawValue in _set_subscript(this.id, index, rawValue.kind, diff --git a/Sources/JavaScriptKit/JSValueConvertible.swift b/Sources/JavaScriptKit/JSValueConvertible.swift index 08897aa92..eb4e27452 100644 --- a/Sources/JavaScriptKit/JSValueConvertible.swift +++ b/Sources/JavaScriptKit/JSValueConvertible.swift @@ -52,12 +52,12 @@ extension String: JSValueConvertible { public func jsValue() -> JSValue { .string(self) } } -extension JSObjectRef: JSValueConvertible { - // `JSObjectRef.jsValue` is defined in JSObjectRef.swift to be able to overridden - // from `JSFunctionRef` +extension JSObject: JSValueConvertible { + // `JSObject.jsValue` is defined in JSObject.swift to be able to overridden + // from `JSFunction` } -private let Object = JSObjectRef.global.Object.function! +private let Object = JSObject.global.Object.function! extension Dictionary where Value: JSValueConvertible, Key == String { public func jsValue() -> JSValue { @@ -75,7 +75,7 @@ extension Dictionary: JSValueConvertible where Value == JSValueConvertible, Key } } -private let Array = JSObjectRef.global.Array.function! +private let Array = JSObject.global.Array.function! extension Array where Element: JSValueConvertible { public func jsValue() -> JSValue { @@ -111,13 +111,13 @@ extension RawJSValue: JSValueConvertible { let string = String(decodingCString: UnsafePointer(buffer), as: UTF8.self) return .string(string) case .object: - return .object(JSObjectRef(id: UInt32(payload1))) + return .object(JSObject(id: UInt32(payload1))) case .null: return .null case .undefined: return .undefined case .function: - return .function(JSFunctionRef(id: UInt32(payload1))) + return .function(JSFunction(id: UInt32(payload1))) default: fatalError("unreachable") } diff --git a/Sources/JavaScriptKit/JSValueDecoder.swift b/Sources/JavaScriptKit/JSValueDecoder.swift index e71a0c991..e5c2d5bbd 100644 --- a/Sources/JavaScriptKit/JSValueDecoder.swift +++ b/Sources/JavaScriptKit/JSValueDecoder.swift @@ -11,12 +11,12 @@ private struct _Decoder: Decoder { let userInfo: [CodingUserInfoKey: Any] func container(keyedBy _: Key.Type) throws -> KeyedDecodingContainer where Key: CodingKey { - guard let ref = node.object else { throw _typeMismatch(at: codingPath, JSObjectRef.self, reality: node) } + guard let ref = node.object else { throw _typeMismatch(at: codingPath, JSObject.self, reality: node) } return KeyedDecodingContainer(_KeyedDecodingContainer(decoder: self, ref: ref)) } func unkeyedContainer() throws -> UnkeyedDecodingContainer { - guard let ref = node.object else { throw _typeMismatch(at: codingPath, JSObjectRef.self, reality: node) } + guard let ref = node.object else { throw _typeMismatch(at: codingPath, JSObject.self, reality: node) } return _UnkeyedDecodingContainer(decoder: self, ref: ref) } @@ -34,8 +34,8 @@ private struct _Decoder: Decoder { } private enum Object { - static let ref = JSObjectRef.global.Object.object! - static func keys(_ object: JSObjectRef) -> [String] { + static let ref = JSObject.global.Object.object! + static func keys(_ object: JSObject) -> [String] { let keys = ref.keys!(object).array! return keys.map { $0.string! } } @@ -77,14 +77,14 @@ struct _JSCodingKey: CodingKey { private struct _KeyedDecodingContainer: KeyedDecodingContainerProtocol { private let decoder: _Decoder - private let ref: JSObjectRef + private let ref: JSObject var codingPath: [CodingKey] { return decoder.codingPath } var allKeys: [Key] { Object.keys(ref).compactMap(Key.init(stringValue:)) } - init(decoder: _Decoder, ref: JSObjectRef) { + init(decoder: _Decoder, ref: JSObject) { self.decoder = decoder self.ref = ref } @@ -152,9 +152,9 @@ private struct _UnkeyedDecodingContainer: UnkeyedDecodingContainer { private var currentKey: CodingKey { return _JSCodingKey(index: currentIndex) } let decoder: _Decoder - let ref: JSObjectRef + let ref: JSObject - init(decoder: _Decoder, ref: JSObjectRef) { + init(decoder: _Decoder, ref: JSObject) { self.decoder = decoder count = ref.length.number.map(Int.init) self.ref = ref From 698ef2f3cb8f4f8ff38365fcd1a9f2f847db23e4 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 28 Aug 2020 10:41:32 +0900 Subject: [PATCH 03/11] Throw error and exit non-zero status when test failed --- .../Sources/PrimaryTests/UnitTestUtils.swift | 3 ++- .../Sources/PrimaryTests/main.swift | 26 +++++++++---------- IntegrationTests/bin/primary-tests.js | 1 + 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift index becf261ba..1e4c74554 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift @@ -5,13 +5,14 @@ var printTestNames = false // This will make it easier to debug any errors that occur on the JS side. //printTestNames = true -func test(_ name: String, testBlock: () throws -> Void) { +func test(_ name: String, testBlock: () throws -> Void) throws { if printTestNames { print(name) } do { try testBlock() } catch { print("Error in \(name)") print(error) + throw error } } diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 16a78d4bc..3e3881da7 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -1,6 +1,6 @@ import JavaScriptKit -test("Literal Conversion") { +try test("Literal Conversion") { let global = JSObject.global let inputs: [JSValue] = [ .boolean(true), @@ -29,7 +29,7 @@ test("Literal Conversion") { } } -test("Object Conversion") { +try test("Object Conversion") { // Notes: globalObject1 is defined in JavaScript environment // // ```js @@ -70,7 +70,7 @@ test("Object Conversion") { try expectEqual(getJSValue(this: globalObject1Ref, name: "undefined_prop"), .undefined) } -test("Value Construction") { +try test("Value Construction") { let globalObject1 = getJSValue(this: .global, name: "globalObject1") let globalObject1Ref = try expectObject(globalObject1) let prop_2 = getJSValue(this: globalObject1Ref, name: "prop_2") @@ -82,7 +82,7 @@ test("Value Construction") { try expectEqual(Float.construct(from: prop_7), 3.14) } -test("Array Iterator") { +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") @@ -93,7 +93,7 @@ test("Array Iterator") { try expectEqual(Array(array), expectedProp_4) } -test("Array RandomAccessCollection") { +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") @@ -104,7 +104,7 @@ test("Array RandomAccessCollection") { try expectEqual([array[0], array[1], array[2], array[3]], expectedProp_4) } -test("Value Decoder") { +try test("Value Decoder") { struct GlobalObject1: Codable { struct Prop1: Codable { let nested_prop: Int @@ -124,7 +124,7 @@ test("Value Decoder") { try expectEqual(globalObject1.prop_7, 3.14) } -test("Function Call") { +try test("Function Call") { // Notes: globalObject1 is defined in JavaScript environment // // ```js @@ -168,7 +168,7 @@ test("Function Call") { try expectEqual(func6(true, "OK", 2), .string("OK")) } -test("Host Function Registration") { +try test("Host Function Registration") { // ```js // global.globalObject1 = { // ... @@ -213,7 +213,7 @@ test("Host Function Registration") { hostFunc2.release() } -test("New Object Construction") { +try test("New Object Construction") { // ```js // global.Animal = function(name, age, isCat) { // this.name = name @@ -237,7 +237,7 @@ test("New Object Construction") { try expectEqual(dog1Bark(), .string("wan")) } -test("Call Function With This") { +try test("Call Function With This") { // ```js // global.Animal = function(name, age, isCat) { // this.name = name @@ -263,7 +263,7 @@ test("Call Function With This") { try expectEqual(gotIsCat, .boolean(true)) } -test("Object Conversion") { +try test("Object Conversion") { let array1 = [1, 2, 3] let jsArray1 = array1.jsValue().object! try expectEqual(jsArray1.length, .number(3)) @@ -292,7 +292,7 @@ test("Object Conversion") { try expectEqual(jsDict1.prop2, .string("foo")) } -test("ObjectRef Lifetime") { +try test("ObjectRef Lifetime") { // ```js // global.globalObject1 = { // "prop_1": { @@ -322,7 +322,7 @@ func closureScope() -> ObjectIdentifier { return result } -test("Closure Identifiers") { +try test("Closure Identifiers") { let oid1 = closureScope() let oid2 = closureScope() try expectEqual(oid1, oid2) diff --git a/IntegrationTests/bin/primary-tests.js b/IntegrationTests/bin/primary-tests.js index 094ee4854..9617178ca 100644 --- a/IntegrationTests/bin/primary-tests.js +++ b/IntegrationTests/bin/primary-tests.js @@ -41,4 +41,5 @@ const { startWasiTask } = require("../lib") startWasiTask("./dist/PrimaryTests.wasm").catch(err => { console.log(err) + process.exit(1) }); From c201a3ee2c91265ecf9b1070efdad06f5a96f146 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 28 Aug 2020 10:42:05 +0900 Subject: [PATCH 04/11] Fix deprecated warnings --- .../TestSuites/Sources/PrimaryTests/UnitTestUtils.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift index 1e4c74554..3ef17e2bb 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift @@ -53,7 +53,7 @@ func expectArray(_ value: JSValue, file: StaticString = #file, line: UInt = #lin return array } -func expectFunction(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSFunctionRef { +func expectFunction(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSFunction { switch value { case let .function(ref): return ref default: From 3314250059c32b627a8edf67bc071ea6094aaf14 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 28 Aug 2020 10:44:17 +0900 Subject: [PATCH 05/11] Fix uncached unit test failsure --- IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 3e3881da7..22fc89fe6 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -60,7 +60,7 @@ try test("Object Conversion") { let prop_4 = getJSValue(this: globalObject1Ref, name: "prop_4") let prop_4Array = try expectObject(prop_4) let expectedProp_4: [JSValue] = [ - .number(3), .number(4), .string("str_elm_1"), .number(5), + .number(3), .number(4), .string("str_elm_1"), .null, .undefined, .number(5), ] for (index, expectedElement) in expectedProp_4.enumerated() { let actualElement = getJSValue(this: prop_4Array, index: Int32(index)) @@ -99,9 +99,9 @@ try test("Array RandomAccessCollection") { let prop_4 = getJSValue(this: globalObject1Ref, name: "prop_4") let array = try expectArray(prop_4) let expectedProp_4: [JSValue] = [ - .number(3), .number(4), .string("str_elm_1"), .number(5), + .number(3), .number(4), .string("str_elm_1"), .null, .undefined, .number(5), ] - try expectEqual([array[0], array[1], array[2], array[3]], expectedProp_4) + try expectEqual([array[0], array[1], array[2], array[3], array[4], array[5]], expectedProp_4) } try test("Value Decoder") { From c67bd51b966fb2cab9774960a280c4fcd0b5fafd Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 29 Aug 2020 10:09:39 +0900 Subject: [PATCH 06/11] Support Holes in Array (#41) * Support Holes in Array * Use filter(() => true) * Revert accidently changed line --- .../Sources/PrimaryTests/main.swift | 22 ++++++++-- IntegrationTests/bin/primary-tests.js | 1 + .../JavaScriptKit/BasicObjects/JSArray.swift | 41 +++++++++++++++++-- 3 files changed, 56 insertions(+), 8 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..4a8a6aecc 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 } } @@ -43,7 +47,36 @@ extension JSArray: RandomAccessCollection { public var startIndex: Int { 0 } - public var endIndex: Int { ref.length.number.map(Int.init) ?? 0 } + public var endIndex: Int { length } + + /// 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 let alwaysTrue = JSClosure { _ in .boolean(true) } +private func getObjectValuesLength(_ object: JSObject) -> Int { + let values = object.filter!(alwaysTrue).object! + return Int(values.length.number!) } extension JSValue { From a18e83de17939a8752e71f551be50779f537a068 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2020 20:32:11 +0000 Subject: [PATCH 07/11] Bump bl from 3.0.0 to 3.0.1 in /Example (#43) --- Example/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Example/package-lock.json b/Example/package-lock.json index 2282e8b9c..ee114f0e2 100644 --- a/Example/package-lock.json +++ b/Example/package-lock.json @@ -541,9 +541,9 @@ } }, "bl": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-3.0.0.tgz", - "integrity": "sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-3.0.1.tgz", + "integrity": "sha512-jrCW5ZhfQ/Vt07WX1Ngs+yn9BDqPL/gw28S7s9H6QK/gupnizNzJAss5akW20ISgOrbLTlXOOCTJeNUQqruAWQ==", "requires": { "readable-stream": "^3.0.1" } From 445ea5bfea2ad3fdf46e8d852935ce084114e8bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2020 20:32:56 +0000 Subject: [PATCH 08/11] Bump bl from 3.0.0 to 3.0.1 in /IntegrationTests (#42) --- IntegrationTests/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/IntegrationTests/package-lock.json b/IntegrationTests/package-lock.json index 01f0a8232..b5e4e6b2c 100644 --- a/IntegrationTests/package-lock.json +++ b/IntegrationTests/package-lock.json @@ -24,9 +24,9 @@ } }, "bl": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-3.0.0.tgz", - "integrity": "sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-3.0.1.tgz", + "integrity": "sha512-jrCW5ZhfQ/Vt07WX1Ngs+yn9BDqPL/gw28S7s9H6QK/gupnizNzJAss5akW20ISgOrbLTlXOOCTJeNUQqruAWQ==", "requires": { "readable-stream": "^3.0.1" } From d64def7bf31921ad8338d95ff602c2616425bf32 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 9 Sep 2020 11:35:14 +0900 Subject: [PATCH 09/11] Remove manual reference counting --- .../{ => BasicObjects}/JSTypedArray.swift | 29 ++++++++++--------- Sources/JavaScriptKit/XcodeSupport.swift | 1 - .../_CJavaScriptKit/include/_CJavaScriptKit.h | 4 --- 3 files changed, 16 insertions(+), 18 deletions(-) rename Sources/JavaScriptKit/{ => BasicObjects}/JSTypedArray.swift (84%) diff --git a/Sources/JavaScriptKit/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift similarity index 84% rename from Sources/JavaScriptKit/JSTypedArray.swift rename to Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index b6488e0dc..3806cfc28 100644 --- a/Sources/JavaScriptKit/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -9,37 +9,40 @@ public protocol TypedArrayElement: JSValueConvertible, JSValueConstructible { static var typedArrayClass: JSFunctionRef { get } } -public class JSTypedArray: JSObjectRef, ExpressibleByArrayLiteral where Element: TypedArrayElement { +public class JSTypedArray: JSValueConvertible, ExpressibleByArrayLiteral where Element: TypedArrayElement { + let ref: JSObject + public func jsValue() -> JSValue { + .object(ref) + } + public subscript(_ index: Int) -> Element { get { - return Element.construct(from: getJSValue(this: self, index: Int32(index)))! + return Element.construct(from: getJSValue(this: ref, index: Int32(index)))! } set { - setJSValue(this: self, index: Int32(index), value: newValue.jsValue()) + setJSValue(this: ref, index: Int32(index), value: newValue.jsValue()) } } + + public init(_ object: JSObject) { + self.ref = object + } - public init(length: Int) { + public convenience init(length: Int) { let jsObject = Element.typedArrayClass.new(length) - // _retain is necessary here because the JSObjectRef we used to create the array - // goes out of scope and is deinitialized when this init() returns, causing - // the JS side to decrement the object's reference count. JSTypedArray will also - // call _release() when deinitialized because it inherits from JSObjectRef, so this - // will not leak memory. - _retain(jsObject.id) - super.init(id: jsObject.id) + self.init(jsObject) } required public convenience init(arrayLiteral elements: Element...) { self.init(elements) } - public init(_ array: [Element]) { + public convenience init(_ array: [Element]) { var resultObj = JavaScriptObjectRef() array.withUnsafeBufferPointer { ptr in _create_typed_array(Element.typedArrayKind, ptr.baseAddress!, Int32(array.count), &resultObj) } - super.init(id: resultObj) + self.init(JSObject(id: resultObj)) } public convenience init(_ stride: StrideTo) where Element: Strideable { diff --git a/Sources/JavaScriptKit/XcodeSupport.swift b/Sources/JavaScriptKit/XcodeSupport.swift index 371c1f74c..ed42417c8 100644 --- a/Sources/JavaScriptKit/XcodeSupport.swift +++ b/Sources/JavaScriptKit/XcodeSupport.swift @@ -72,7 +72,6 @@ import _CJavaScriptKit _: JavaScriptHostFuncRef, _: UnsafePointer! ) { fatalError() } - func _retain(_: JavaScriptObjectRef) { fatalError() } func _release(_: JavaScriptObjectRef) { fatalError() } func _create_typed_array( _: JavaScriptTypedArrayKind, diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h index 7e12d0e50..9bdfc9b63 100644 --- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h +++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h @@ -106,10 +106,6 @@ __attribute__((__import_module__("javascript_kit"), _create_function(const JavaScriptHostFuncRef host_func_id, const JavaScriptObjectRef *func_ref_ptr); -__attribute__((__import_module__("javascript_kit"), - __import_name__("swjs_retain"))) extern void -_retain(const JavaScriptObjectRef ref); - __attribute__((__import_module__("javascript_kit"), __import_name__("swjs_release"))) extern void _release(const JavaScriptObjectRef ref); From fab45e149b185ed59b32a2c7d38b801f4999c77e Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 9 Sep 2020 11:37:11 +0900 Subject: [PATCH 10/11] Fix test cases --- .../TestSuites/Sources/PrimaryTests/main.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 3be0a6f90..292e6f3a5 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -343,14 +343,18 @@ try test("Closure Identifiers") { } func checkArray(_ array: [T]) throws where T: TypedArrayElement { - try expectEqual(JSTypedArray(array).toString!(), .string(jsStringify(array))) + try expectEqual(toString(JSTypedArray(array).jsValue().object!), jsStringify(array)) +} + +func toString(_ object: T) -> String { + return object.toString!().string! } func jsStringify(_ array: [Any]) -> String { array.map({ String(describing: $0) }).joined(separator: ",") } -test("TypedArray") { +try test("TypedArray") { let numbers = [UInt8](0 ... 255) let typedArray = JSTypedArray(numbers) try expectEqual(typedArray[12], 12) @@ -380,7 +384,7 @@ test("TypedArray") { } } -test("TypedArray_Mutation") { +try test("TypedArray_Mutation") { let array = JSTypedArray(length: 100) for i in 0..<100 { array[i] = i @@ -388,5 +392,5 @@ test("TypedArray_Mutation") { for i in 0..<100 { try expectEqual(i, array[i]) } - try expectEqual(array.toString!(), .string(jsStringify(Array(0..<100)))) + try expectEqual(toString(array.jsValue().object!), jsStringify(Array(0..<100))) } From f83a84c36bd9f006615a39a8e1de41be42130216 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 10 Sep 2020 15:58:09 +0900 Subject: [PATCH 11/11] Expose failable initializer --- .../JavaScriptKit/BasicObjects/JSTypedArray.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index 3806cfc28..beb430a5f 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -24,13 +24,19 @@ public class JSTypedArray: JSValueConvertible, ExpressibleByArrayLitera } } - public init(_ object: JSObject) { + // This private initializer assumes that the passed object is TypedArray + private init(unsafe object: JSObject) { + self.ref = object + } + + public init?(_ object: JSObject) { + guard object.isInstanceOf(Element.typedArrayClass) else { return nil } self.ref = object } public convenience init(length: Int) { let jsObject = Element.typedArrayClass.new(length) - self.init(jsObject) + self.init(unsafe: jsObject) } required public convenience init(arrayLiteral elements: Element...) { @@ -42,7 +48,7 @@ public class JSTypedArray: JSValueConvertible, ExpressibleByArrayLitera array.withUnsafeBufferPointer { ptr in _create_typed_array(Element.typedArrayKind, ptr.baseAddress!, Int32(array.count), &resultObj) } - self.init(JSObject(id: resultObj)) + self.init(unsafe: JSObject(id: resultObj)) } public convenience init(_ stride: StrideTo) where Element: Strideable {