From 301dc00727b69b1549fd02fa8b2513ca9149278f Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 16 Sep 2020 18:33:49 +0900 Subject: [PATCH 1/3] Add doc comments for public APIs (Part 1) --- .../FundamentalObjects/JSFunction.swift | 74 ++++++++++++++++--- .../FundamentalObjects/JSObject.swift | 47 +++++++++++- Sources/JavaScriptKit/JSValue.swift | 48 +++++++++++- 3 files changed, 153 insertions(+), 16 deletions(-) diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift b/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift index 92a52d0be..204e18972 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift @@ -1,6 +1,22 @@ import _CJavaScriptKit +/// `JSFunction` represents a function in JavaScript and supports new object instantiation. +/// This type can be callable as a function using `callAsFunction`. +/// +/// e.g. +/// ```swift +/// let alert: JSFunction = JSObject.global.alert.function! +/// // Call `JSFunction` as a function +/// alert("Hello, world") +/// ``` +/// public class JSFunction: JSObject { + + /// Call this function with given `arguments` and binding given `this` as context. + /// - Parameters: + /// - this: The value to be passed as the `this` parameter to this function. + /// - arguments: Arguments to be passed to this function. + /// - Returns: The result of this call. @discardableResult public func callAsFunction(this: JSObject? = nil, arguments: [JSValueConvertible]) -> JSValue { let result = arguments.withRawJSValues { rawValues in @@ -24,19 +40,22 @@ public class JSFunction: JSObject { return result.jsValue() } + /// A variadic arguments version of `callAsFunction`. @discardableResult public func callAsFunction(this: JSObject? = nil, _ arguments: JSValueConvertible...) -> JSValue { self(this: this, arguments: arguments) } - public func new(_ arguments: JSValueConvertible...) -> JSObject { - new(arguments: arguments) - } - - // Guaranteed to return an object because either: - // 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`. + /// Instantiate an object from this function as a constructor. + /// + /// Guaranteed to return an object because either: + /// + /// - 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`. + /// + /// - Parameter arguments: Arguments to be passed to this constructor function. + /// - Returns: A new instance of this constructor. public func new(arguments: [JSValueConvertible]) -> JSObject { arguments.withRawJSValues { rawValues in rawValues.withUnsafeBufferPointer { bufferPointer in @@ -52,6 +71,11 @@ public class JSFunction: JSObject { } } + /// A variadic arguments version of `new`. + public func new(_ arguments: JSValueConvertible...) -> JSObject { + new(arguments: arguments) + } + @available(*, unavailable, message: "Please use JSClosure instead") public static func from(_: @escaping ([JSValue]) -> JSValue) -> JSFunction { fatalError("unavailable") @@ -62,33 +86,59 @@ public class JSFunction: JSObject { } } +/// `JSClosure` represents a JavaScript function whose body is written in Swift. +/// This type can be passed as a callback handler to JavaScript functions. +/// Note that the lifetime of `JSClosure` should be managed by users manually +/// due to GC boundary between Swift and JavaScript. +/// For further discussion, see also [swiftwasm/JavaScriptKit #33](https://github.com/swiftwasm/JavaScriptKit/pull/33) +/// +/// e.g. +/// ```swift +/// let eventListenter = JSClosure { _ in +/// ... +/// return JSValue.undefined +/// } +/// +/// button.addEventListener!("click", JSValue.function(eventListenter)) +/// ... +/// button.removeEventListener!("click", JSValue.function(eventListenter)) +/// eventListenter.release() +/// ``` +/// public class JSClosure: JSFunction { static var sharedFunctions: [JavaScriptHostFuncRef: ([JSValue]) -> JSValue] = [:] private var hostFuncRef: JavaScriptHostFuncRef = 0 private var isReleased = false - + + /// Instantiate a new `JSClosure` with given function body. + /// - Parameter body: The body of this function. public init(_ body: @escaping ([JSValue]) -> JSValue) { + // 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`. super.init(id: 0) let objectId = ObjectIdentifier(self) let funcRef = JavaScriptHostFuncRef(bitPattern: Int32(objectId.hashValue)) + // 2. Retain the given body in static storage by `funcRef`. Self.sharedFunctions[funcRef] = body - + // 3. Create a new JavaScript function which calls the given Swift function. var objectRef: JavaScriptObjectRef = 0 _create_function(funcRef, &objectRef) hostFuncRef = funcRef id = objectRef } - + + /// A convenience initializer which assumes that the given body function returns `JSValue.undefined` convenience public init(_ body: @escaping ([JSValue]) -> ()) { self.init { (arguments: [JSValue]) -> JSValue in body(arguments) return .undefined } } - + + /// Release this function resource. + /// After calling `release`, calling this function from JavaScript will fail. public func release() { Self.sharedFunctions[hostFuncRef] = nil isReleased = true diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift index 40bc4658b..da4e9814d 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift @@ -1,12 +1,36 @@ import _CJavaScriptKit +/// `JSObject` represents an object in JavaScript and supports dynamic member lookup. +/// Any member access like `object.foo` will dynamically request the JavaScript and Swift +/// runtime bridge library for a member with the specified name in this object. +/// +/// And this object supports to call a member method of the object. +/// +/// e.g. +/// ```swift +/// let document = JSObject.global.document.object! +/// let divElement = document.createElement!("div") +/// ``` +/// +/// The lifetime of this object is managed by the JavaScript and Swift runtime bridge library with +/// reference counting system. @dynamicMemberLookup public class JSObject: Equatable { - internal var id: UInt32 - init(id: UInt32) { + internal var id: JavaScriptObjectRef + init(id: JavaScriptObjectRef) { self.id = id } + /// Returns the `name` member method binding this object as `this` context. + /// + /// e.g. + /// ```swift + /// let document = JSObject.global.document.object! + /// let divElement = document.createElement!("div") + /// ``` + /// + /// - Parameter name: The name of this object's member to access. + /// - Returns: The `name` member method binding this object as `this` context. @_disfavoredOverload public subscript(dynamicMember name: String) -> ((JSValueConvertible...) -> JSValue)? { guard let function = self[name].function else { return nil } @@ -15,30 +39,49 @@ public class JSObject: Equatable { } } + /// A convenience method of `subscript(_ name: String) -> JSValue` + /// to access the member through Dynamic Member Lookup. public subscript(dynamicMember name: String) -> JSValue { get { self[name] } set { self[name] = newValue } } + /// Access the `name` member dynamically through JavaScript and Swift runtime bridge library. + /// - Parameter name: The name of this object's member to access. + /// - Returns: The value of the `name` member of this object. public subscript(_ name: String) -> JSValue { get { getJSValue(this: self, name: name) } set { setJSValue(this: self, name: name, value: newValue) } } + /// Access the `index` member dynamically through JavaScript and Swift runtime bridge library. + /// - Parameter index: The index of this object's member to access. + /// - Returns: The value of the `index` member of this object. public subscript(_ index: Int) -> JSValue { get { getJSValue(this: self, index: Int32(index)) } set { setJSValue(this: self, index: Int32(index), value: newValue) } } + /// Return `true` if this object is an instance of the `constructor`. Return `false`, if not. + /// - Parameter constructor: The constructor function to check. + /// - Returns: The result of `instanceof` in JavaScript environment. public func isInstanceOf(_ constructor: JSFunction) -> Bool { _instanceof(id, constructor.id) } static let _JS_Predef_Value_Global: JavaScriptObjectRef = 0 + + /// A `JSObject` of the global scope object. + /// This allows access to the global properties and global names by accessing the `JSObject` returned. public static let global = JSObject(id: _JS_Predef_Value_Global) deinit { _release(id) } + /// Returns a Boolean value indicating whether two values points same objects. + /// + /// - Parameters: + /// - lhs: A object to compare. + /// - rhs: Another object to compare. 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 4ac7a4579..15aac0ca4 100644 --- a/Sources/JavaScriptKit/JSValue.swift +++ b/Sources/JavaScriptKit/JSValue.swift @@ -1,5 +1,6 @@ import _CJavaScriptKit +/// `JSValue` represents a value in JavaScript. public enum JSValue: Equatable { case boolean(Bool) case string(String) @@ -9,6 +10,8 @@ public enum JSValue: Equatable { case undefined case function(JSFunction) + /// Returns the `Bool` value of this JS value if its type is boolean. + /// If not, returns `nil`. public var boolean: Bool? { switch self { case let .boolean(boolean): return boolean @@ -16,6 +19,8 @@ public enum JSValue: Equatable { } } + /// Returns the `String` value of this JS value if the type is string. + /// If not, returns `nil`. public var string: String? { switch self { case let .string(string): return string @@ -23,6 +28,8 @@ public enum JSValue: Equatable { } } + /// Returns the `Double` value of this JS value if the type is number. + /// If not, returns `nil`. public var number: Double? { switch self { case let .number(number): return number @@ -30,6 +37,8 @@ public enum JSValue: Equatable { } } + /// Returns the `JSObject` of this JS value if its type is object. + /// If not, returns `nil`. public var object: JSObject? { switch self { case let .object(object): return object @@ -37,17 +46,52 @@ public enum JSValue: Equatable { } } - public var isNull: Bool { return self == .null } - public var isUndefined: Bool { return self == .undefined } + /// Returns the `JSFunction` of this JS value if its type is function. + /// If not, returns `nil`. public var function: JSFunction? { switch self { case let .function(function): return function default: return nil } } + + /// Returns the `true` if this JS value is null. + /// If not, returns `false`. + public var isNull: Bool { return self == .null } + + /// Returns the `true` if this JS value is undefined. + /// If not, returns `false`. + public var isUndefined: Bool { return self == .undefined } + } extension JSValue { + + /// Deprecated: Please create `JSClosure` directly and manage its lifetime manually. + /// + /// Migrate this usage + /// + /// ```swift + /// button.addEventListener!("click", JSValue.function { _ in + /// ... + /// return JSValue.undefined + /// }) + /// ``` + /// + /// into below code. + /// + /// ```swift + /// let eventListenter = JSClosure { _ in + /// ... + /// return JSValue.undefined + /// } + /// + /// button.addEventListener!("click", JSValue.function(eventListenter)) + /// ... + /// button.removeEventListener!("click", JSValue.function(eventListenter)) + /// eventListenter.release() + /// ``` + @available(*, deprecated, message: "Please create JSClosure directly and manage its lifetime manually.") public static func function(_ body: @escaping ([JSValue]) -> JSValue) -> JSValue { .function(JSClosure(body)) } From 26bc9d8279813038a05a73be221f0480f7a7b22e Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 16 Sep 2020 20:19:07 +0900 Subject: [PATCH 2/3] Update Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift Co-authored-by: Max Desiatov --- Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift b/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift index 204e18972..71cec9d58 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift @@ -86,7 +86,7 @@ public class JSFunction: JSObject { } } -/// `JSClosure` represents a JavaScript function whose body is written in Swift. +/// `JSClosure` represents a JavaScript function the body of which is written in Swift. /// This type can be passed as a callback handler to JavaScript functions. /// Note that the lifetime of `JSClosure` should be managed by users manually /// due to GC boundary between Swift and JavaScript. From 5f2eac95672a0d971392c440f5284083a52f6ba2 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 16 Sep 2020 20:19:14 +0900 Subject: [PATCH 3/3] Update Sources/JavaScriptKit/FundamentalObjects/JSObject.swift Co-authored-by: Max Desiatov --- Sources/JavaScriptKit/FundamentalObjects/JSObject.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift index da4e9814d..0d3532ab5 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift @@ -77,7 +77,7 @@ public class JSObject: Equatable { deinit { _release(id) } - /// Returns a Boolean value indicating whether two values points same objects. + /// Returns a Boolean value indicating whether two values point to same objects. /// /// - Parameters: /// - lhs: A object to compare.