Skip to content

Commit

Permalink
Merge pull request #55 from swiftwasm/add-doc-comments
Browse files Browse the repository at this point in the history
Add doc comments for public APIs (Part 1)
  • Loading branch information
kateinoigakukun authored Sep 16, 2020
2 parents 51331c6 + 5f2eac9 commit 2eade6f
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 16 deletions.
74 changes: 62 additions & 12 deletions Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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")
Expand All @@ -62,33 +86,59 @@ public class JSFunction: JSObject {
}
}

/// `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.
/// 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
Expand Down
47 changes: 45 additions & 2 deletions Sources/JavaScriptKit/FundamentalObjects/JSObject.swift
Original file line number Diff line number Diff line change
@@ -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 }
Expand All @@ -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 point to 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
}
Expand Down
48 changes: 46 additions & 2 deletions Sources/JavaScriptKit/JSValue.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import _CJavaScriptKit

/// `JSValue` represents a value in JavaScript.
public enum JSValue: Equatable {
case boolean(Bool)
case string(String)
Expand All @@ -9,45 +10,88 @@ 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
default: return nil
}
}

/// 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
default: return nil
}
}

/// 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
default: return nil
}
}

/// 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
default: return nil
}
}

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))
}
Expand Down

0 comments on commit 2eade6f

Please sign in to comment.