Skip to content

Add doc comments for public APIs (Part 1) #55

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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