Skip to content
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
2 changes: 1 addition & 1 deletion Sources/JavaScriptKit/BasicObjects/JSPromise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public final class JSPromise: JSBridgedClass {
/// is not an object and is not an instance of JavaScript `Promise`, this function will
/// return `nil`.
public static func construct(from value: JSValue) -> Self? {
guard case .object(let jsObject) = value else { return nil }
guard let jsObject = value.object else { return nil }
return Self(jsObject)
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/JavaScriptKit/ConvertibleToJSValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ extension JSValue {
let kind: JavaScriptValueKind
let payload1: JavaScriptPayload1
var payload2: JavaScriptPayload2 = 0
switch self {
switch self.storage {
case .boolean(let boolValue):
kind = .boolean
payload1 = boolValue ? 1 : 0
Expand Down
2 changes: 1 addition & 1 deletion Sources/JavaScriptKit/FundamentalObjects/JSObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ public class JSObject: Equatable, ExpressibleByDictionaryLiteral {
}

public static func construct(from value: JSValue) -> Self? {
switch value {
switch value.storage {
case .boolean,
.string,
.number,
Expand Down
87 changes: 61 additions & 26 deletions Sources/JavaScriptKit/JSValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,55 @@ import _CJavaScriptKit

/// `JSValue` represents a value in JavaScript.
@dynamicMemberLookup
public enum JSValue: Equatable {
case boolean(Bool)
case string(JSString)
case number(Double)
case object(JSObject)
case null
case undefined
case symbol(JSSymbol)
case bigInt(JSBigInt)
public struct JSValue: Equatable {
/// The internal storage of the JSValue, which is intentionally not public
/// to leave the flexibility to change the storage.
internal enum Storage: Equatable {
case boolean(Bool)
case string(JSString)
case number(Double)
case object(JSObject)
case null
case undefined
case symbol(JSSymbol)
case bigInt(JSBigInt)
}

internal var storage: Storage

internal init(storage: Storage) {
self.storage = storage
}

public static func boolean(_ value: Bool) -> JSValue {
.init(storage: .boolean(value))
}
public static func string(_ value: JSString) -> JSValue {
.init(storage: .string(value))
}
public static func number(_ value: Double) -> JSValue {
.init(storage: .number(value))
}
public static func object(_ value: JSObject) -> JSValue {
.init(storage: .object(value))
}
public static var null: JSValue {
.init(storage: .null)
}
public static var undefined: JSValue {
.init(storage: .undefined)
}
public static func symbol(_ value: JSSymbol) -> JSValue {
.init(storage: .symbol(value))
}
public static func bigInt(_ value: JSBigInt) -> JSValue {
.init(storage: .bigInt(value))
}

/// Returns the `Bool` value of this JS value if its type is boolean.
/// If not, returns `nil`.
public var boolean: Bool? {
switch self {
switch storage {
case .boolean(let boolean): return boolean
default: return nil
}
Expand All @@ -35,7 +70,7 @@ public enum JSValue: Equatable {
/// If not, returns `nil`.
///
public var jsString: JSString? {
switch self {
switch storage {
case .string(let string): return string
default: return nil
}
Expand All @@ -44,7 +79,7 @@ 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 {
switch storage {
case .number(let number): return number
default: return nil
}
Expand All @@ -53,7 +88,7 @@ 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 {
switch storage {
case .object(let object): return object
default: return nil
}
Expand All @@ -65,7 +100,7 @@ public enum JSValue: Equatable {
/// Returns the `JSSymbol` of this JS value if its type is function.
/// If not, returns `nil`.
public var symbol: JSSymbol? {
switch self {
switch storage {
case .symbol(let symbol): return symbol
default: return nil
}
Expand All @@ -74,7 +109,7 @@ public enum JSValue: Equatable {
/// Returns the `JSBigInt` of this JS value if its type is function.
/// If not, returns `nil`.
public var bigInt: JSBigInt? {
switch self {
switch storage {
case .bigInt(let bigInt): return bigInt
default: return nil
}
Expand All @@ -83,13 +118,13 @@ public enum JSValue: Equatable {
/// Returns the `true` if this JS value is null.
/// If not, returns `false`.
public var isNull: Bool {
return self == .null
return storage == .null
}

/// Returns the `true` if this JS value is undefined.
/// If not, returns `false`.
public var isUndefined: Bool {
return self == .undefined
return storage == .undefined
}
}

Expand Down Expand Up @@ -132,7 +167,7 @@ extension JSValue {

extension JSValue {
public static func string(_ value: String) -> JSValue {
.string(JSString(value))
.init(storage: .string(JSString(value)))
}

/// Deprecated: Please create `JSClosure` directly and manage its lifetime manually.
Expand Down Expand Up @@ -161,7 +196,7 @@ extension JSValue {
/// ```
@available(*, deprecated, message: "Please create JSClosure directly and manage its lifetime manually.")
public static func function(_ body: @escaping ([JSValue]) -> JSValue) -> JSValue {
.object(JSClosure(body))
.init(storage: .object(JSClosure(body)))
}

@available(
Expand All @@ -171,34 +206,34 @@ extension JSValue {
message: "JSClosure is no longer a subclass of JSFunction. Use .object(closure) instead."
)
public static func function(_ closure: JSClosure) -> JSValue {
.object(closure)
.init(storage: .object(closure))
}

@available(*, deprecated, renamed: "object", message: "Use .object(function) instead")
public static func function(_ function: JSObject) -> JSValue { .object(function) }
public static func function(_ function: JSObject) -> JSValue { .init(storage: .object(function)) }
}

extension JSValue: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
self = .string(JSString(value))
self = .init(storage: .string(JSString(value)))
}
}

extension JSValue: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int32) {
self = .number(Double(value))
self = .init(storage: .number(Double(value)))
}
}

extension JSValue: ExpressibleByFloatLiteral {
public init(floatLiteral value: Double) {
self = .number(value)
self = .init(storage: .number(value))
}
}

extension JSValue: ExpressibleByNilLiteral {
public init(nilLiteral _: ()) {
self = .null
self = .init(storage: .null)
}
}

Expand Down Expand Up @@ -268,7 +303,7 @@ extension JSValue {
/// - Parameter constructor: The constructor function to check.
/// - Returns: The result of `instanceof` in the JavaScript environment.
public func isInstanceOf(_ constructor: JSObject) -> Bool {
switch self {
switch storage {
case .boolean, .string, .number, .null, .undefined, .symbol, .bigInt:
return false
case .object(let ref):
Expand Down
6 changes: 5 additions & 1 deletion Sources/JavaScriptKit/JSValueDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,11 @@ private struct _UnkeyedDecodingContainer: UnkeyedDecodingContainer {

init(decoder: _Decoder, ref: JSObject) {
self.decoder = decoder
count = ref.length.number.map(Int.init)
if let count = ref.length.number {
self.count = Int(count)
} else {
self.count = nil
}
self.ref = ref
}

Expand Down
4 changes: 2 additions & 2 deletions Tests/JavaScriptKitTests/JavaScriptKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ class JavaScriptKitTests: XCTestCase {
let prop = JSString("prop_\(index)")
setJSValue(this: global, name: prop, value: input)
let got = getJSValue(this: global, name: prop)
switch (got, input) {
case (.number(let lhs), .number(let rhs)):
switch (got.number, input.number) {
case (let lhs?, let rhs?):
// Compare bitPattern because nan == nan is always false
XCTAssertEqual(lhs.bitPattern, rhs.bitPattern)
default:
Expand Down