diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 486f7b6bf..174b873ef 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -67,3 +67,18 @@ jobs: - run: swift build env: DEVELOPER_DIR: /Applications/${{ matrix.xcode }}.app/Contents/Developer/ + + format: + runs-on: ubuntu-latest + container: + image: swift:6.0.3 + steps: + - uses: actions/checkout@v4 + - run: ./Utilities/format.swift + - name: Check for formatting changes + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + git diff --exit-code || { + echo "::error::The formatting changed some files. Please run \`./Utilities/format.swift\` and commit the changes." + exit 1 + } diff --git a/.swift-format b/.swift-format new file mode 100644 index 000000000..22ce671e7 --- /dev/null +++ b/.swift-format @@ -0,0 +1,13 @@ +{ + "version": 1, + "lineLength": 120, + "indentation": { + "spaces": 4 + }, + "lineBreakBeforeEachArgument": true, + "indentConditionalCompilationBlocks": false, + "prioritizeKeepingFunctionOutputTogether": true, + "rules": { + "AlwaysUseLowerCamelCase": false + } +} diff --git a/Examples/ActorOnWebWorker/Package.swift b/Examples/ActorOnWebWorker/Package.swift index 711bf6461..82e87dfdc 100644 --- a/Examples/ActorOnWebWorker/Package.swift +++ b/Examples/ActorOnWebWorker/Package.swift @@ -6,7 +6,7 @@ let package = Package( name: "Example", platforms: [.macOS("15"), .iOS("18"), .watchOS("11"), .tvOS("18"), .visionOS("2")], dependencies: [ - .package(path: "../../"), + .package(path: "../../") ], targets: [ .executableTarget( @@ -15,6 +15,6 @@ let package = Package( .product(name: "JavaScriptKit", package: "JavaScriptKit"), .product(name: "JavaScriptEventLoop", package: "JavaScriptKit"), ] - ), + ) ] ) diff --git a/Examples/ActorOnWebWorker/Sources/MyApp.swift b/Examples/ActorOnWebWorker/Sources/MyApp.swift index 7d362d13e..42a2caeb4 100644 --- a/Examples/ActorOnWebWorker/Sources/MyApp.swift +++ b/Examples/ActorOnWebWorker/Sources/MyApp.swift @@ -144,17 +144,21 @@ final class App { } private func setupEventHandlers() { - indexButton.onclick = .object(JSClosure { [weak self] _ in - guard let self else { return .undefined } - self.performIndex() - return .undefined - }) - - searchButton.onclick = .object(JSClosure { [weak self] _ in - guard let self else { return .undefined } - self.performSearch() - return .undefined - }) + indexButton.onclick = .object( + JSClosure { [weak self] _ in + guard let self else { return .undefined } + self.performIndex() + return .undefined + } + ) + + searchButton.onclick = .object( + JSClosure { [weak self] _ in + guard let self else { return .undefined } + self.performSearch() + return .undefined + } + ) } private func performIndex() { @@ -221,7 +225,8 @@ final class App { "padding: 10px; margin: 5px 0; background: #f5f5f5; border-left: 3px solid blue;" ) resultItem.innerHTML = .string( - "Result \(index + 1): \(result.context)") + "Result \(index + 1): \(result.context)" + ) _ = resultsElement.appendChild(resultItem) } } @@ -245,18 +250,18 @@ final class App { } #if canImport(wasi_pthread) - import wasi_pthread - import WASILibc - - /// Trick to avoid blocking the main thread. pthread_mutex_lock function is used by - /// the Swift concurrency runtime. - @_cdecl("pthread_mutex_lock") - func pthread_mutex_lock(_ mutex: UnsafeMutablePointer) -> Int32 { - // DO NOT BLOCK MAIN THREAD - var ret: Int32 - repeat { - ret = pthread_mutex_trylock(mutex) - } while ret == EBUSY - return ret - } +import wasi_pthread +import WASILibc + +/// Trick to avoid blocking the main thread. pthread_mutex_lock function is used by +/// the Swift concurrency runtime. +@_cdecl("pthread_mutex_lock") +func pthread_mutex_lock(_ mutex: UnsafeMutablePointer) -> Int32 { + // DO NOT BLOCK MAIN THREAD + var ret: Int32 + repeat { + ret = pthread_mutex_trylock(mutex) + } while ret == EBUSY + return ret +} #endif diff --git a/Examples/Basic/Package.swift b/Examples/Basic/Package.swift index f1a80aaaa..6c729741c 100644 --- a/Examples/Basic/Package.swift +++ b/Examples/Basic/Package.swift @@ -13,7 +13,7 @@ let package = Package( name: "Basic", dependencies: [ "JavaScriptKit", - .product(name: "JavaScriptEventLoop", package: "JavaScriptKit") + .product(name: "JavaScriptEventLoop", package: "JavaScriptKit"), ] ) ], diff --git a/Examples/Basic/Sources/main.swift b/Examples/Basic/Sources/main.swift index 98b8a6bb5..8fa9c956c 100644 --- a/Examples/Basic/Sources/main.swift +++ b/Examples/Basic/Sources/main.swift @@ -1,5 +1,5 @@ -import JavaScriptKit import JavaScriptEventLoop +import JavaScriptKit let alert = JSObject.global.alert.function! let document = JSObject.global.document @@ -10,10 +10,12 @@ _ = document.body.appendChild(divElement) var buttonElement = document.createElement("button") buttonElement.innerText = "Alert demo" -buttonElement.onclick = .object(JSClosure { _ in - alert("Swift is running on browser!") - return .undefined -}) +buttonElement.onclick = .object( + JSClosure { _ in + alert("Swift is running on browser!") + return .undefined + } +) _ = document.body.appendChild(buttonElement) @@ -30,19 +32,21 @@ struct Response: Decodable { var asyncButtonElement = document.createElement("button") asyncButtonElement.innerText = "Fetch UUID demo" -asyncButtonElement.onclick = .object(JSClosure { _ in - Task { - do { - let response = try await fetch("https://httpbin.org/uuid").value - let json = try await JSPromise(response.json().object!)!.value - let parsedResponse = try JSValueDecoder().decode(Response.self, from: json) - alert(parsedResponse.uuid) - } catch { - print(error) +asyncButtonElement.onclick = .object( + JSClosure { _ in + Task { + do { + let response = try await fetch("https://httpbin.org/uuid").value + let json = try await JSPromise(response.json().object!)!.value + let parsedResponse = try JSValueDecoder().decode(Response.self, from: json) + alert(parsedResponse.uuid) + } catch { + print(error) + } } - } - return .undefined -}) + return .undefined + } +) _ = document.body.appendChild(asyncButtonElement) diff --git a/Examples/Embedded/Package.swift b/Examples/Embedded/Package.swift index f97638cc8..5ae19adc6 100644 --- a/Examples/Embedded/Package.swift +++ b/Examples/Embedded/Package.swift @@ -6,14 +6,14 @@ let package = Package( name: "Embedded", dependencies: [ .package(name: "JavaScriptKit", path: "../../"), - .package(url: "https://github.com/swiftwasm/swift-dlmalloc", branch: "0.1.0") + .package(url: "https://github.com/swiftwasm/swift-dlmalloc", branch: "0.1.0"), ], targets: [ .executableTarget( name: "EmbeddedApp", dependencies: [ "JavaScriptKit", - .product(name: "dlmalloc", package: "swift-dlmalloc") + .product(name: "dlmalloc", package: "swift-dlmalloc"), ], cSettings: [.unsafeFlags(["-fdeclspec"])], swiftSettings: [ @@ -28,7 +28,7 @@ let package = Package( .unsafeFlags([ "-Xclang-linker", "-nostdlib", "-Xlinker", "--no-entry", - "-Xlinker", "--export-if-defined=__main_argc_argv" + "-Xlinker", "--export-if-defined=__main_argc_argv", ]) ] ) diff --git a/Examples/Embedded/Sources/EmbeddedApp/main.swift b/Examples/Embedded/Sources/EmbeddedApp/main.swift index 2ba81dc63..3f8c18ca6 100644 --- a/Examples/Embedded/Sources/EmbeddedApp/main.swift +++ b/Examples/Embedded/Sources/EmbeddedApp/main.swift @@ -13,11 +13,13 @@ _ = document.body.appendChild(divElement) var buttonElement = document.createElement("button") buttonElement.innerText = "Click me" -buttonElement.onclick = JSValue.object(JSClosure { _ in - count += 1 - divElement.innerText = .string("Count \(count)") - return .undefined -}) +buttonElement.onclick = JSValue.object( + JSClosure { _ in + count += 1 + divElement.innerText = .string("Count \(count)") + return .undefined + } +) _ = document.body.appendChild(buttonElement) diff --git a/Examples/Multithreading/Package.swift b/Examples/Multithreading/Package.swift index 211f359a6..4d1ebde70 100644 --- a/Examples/Multithreading/Package.swift +++ b/Examples/Multithreading/Package.swift @@ -7,7 +7,10 @@ let package = Package( platforms: [.macOS("15"), .iOS("18"), .watchOS("11"), .tvOS("18"), .visionOS("2")], dependencies: [ .package(path: "../../"), - .package(url: "https://github.com/kateinoigakukun/chibi-ray", revision: "c8cab621a3338dd2f8e817d3785362409d3b8cf1"), + .package( + url: "https://github.com/kateinoigakukun/chibi-ray", + revision: "c8cab621a3338dd2f8e817d3785362409d3b8cf1" + ), ], targets: [ .executableTarget( @@ -17,6 +20,6 @@ let package = Package( .product(name: "JavaScriptEventLoop", package: "JavaScriptKit"), .product(name: "ChibiRay", package: "chibi-ray"), ] - ), + ) ] ) diff --git a/Examples/Multithreading/Sources/MyApp/Scene.swift b/Examples/Multithreading/Sources/MyApp/Scene.swift index 5f6d467c5..bddde1715 100644 --- a/Examples/Multithreading/Sources/MyApp/Scene.swift +++ b/Examples/Multithreading/Sources/MyApp/Scene.swift @@ -60,7 +60,7 @@ func createDemoScene(size: Int) -> Scene { surface: .diffuse ) ) - ) + ), ], lights: [ .spherical( @@ -83,7 +83,7 @@ func createDemoScene(size: Int) -> Scene { color: Color(red: 0.8, green: 0.8, blue: 0.8), intensity: 0.2 ) - ) + ), ], shadowBias: 1e-13, maxRecursionDepth: 10 diff --git a/Examples/Multithreading/Sources/MyApp/main.swift b/Examples/Multithreading/Sources/MyApp/main.swift index 29cb89f2f..9a1e09bb4 100644 --- a/Examples/Multithreading/Sources/MyApp/main.swift +++ b/Examples/Multithreading/Sources/MyApp/main.swift @@ -1,6 +1,6 @@ import ChibiRay -import JavaScriptKit import JavaScriptEventLoop +import JavaScriptKit JavaScriptEventLoop.installGlobalExecutor() WebWorkerTaskExecutor.installGlobalExecutor() @@ -8,12 +8,12 @@ WebWorkerTaskExecutor.installGlobalExecutor() func renderInCanvas(ctx: JSObject, image: ImageView) { let imageData = ctx.createImageData!(image.width, image.height).object! let data = imageData.data.object! - + for y in 0...allocate(capacity: scene.width * scene.height) // Initialize the buffer with black color @@ -73,12 +79,15 @@ func render(scene: Scene, ctx: JSObject, renderTimeElement: JSObject, concurrenc } var checkTimer: JSValue? - checkTimer = JSObject.global.setInterval!(JSClosure { _ in - print("Checking thread work...") - renderInCanvas(ctx: ctx, image: imageView) - updateRenderTime() - return .undefined - }, 250) + checkTimer = JSObject.global.setInterval!( + JSClosure { _ in + print("Checking thread work...") + renderInCanvas(ctx: ctx, image: imageView) + updateRenderTime() + return .undefined + }, + 250 + ) await withTaskGroup(of: Void.self) { group in let yStride = scene.height / concurrency @@ -117,10 +126,16 @@ func onClick() async throws { let scene = createDemoScene(size: size) let executor = background ? try await WebWorkerTaskExecutor(numberOfThreads: concurrency) : nil - canvasElement.width = .number(Double(scene.width)) + canvasElement.width = .number(Double(scene.width)) canvasElement.height = .number(Double(scene.height)) - await render(scene: scene, ctx: ctx, renderTimeElement: renderTimeElement, concurrency: concurrency, executor: executor) + await render( + scene: scene, + ctx: ctx, + renderTimeElement: renderTimeElement, + concurrency: concurrency, + executor: executor + ) executor?.terminate() print("Render done") } @@ -130,19 +145,21 @@ func main() async throws { let concurrencyElement = JSObject.global.document.getElementById("concurrency").object! concurrencyElement.value = JSObject.global.navigator.hardwareConcurrency - _ = renderButtonElement.addEventListener!("click", JSClosure { _ in - Task { - try await onClick() + _ = renderButtonElement.addEventListener!( + "click", + JSClosure { _ in + Task { + try await onClick() + } + return JSValue.undefined } - return JSValue.undefined - }) + ) } Task { try await main() } - #if canImport(wasi_pthread) import wasi_pthread import WASILibc diff --git a/Examples/OffscrenCanvas/Package.swift b/Examples/OffscrenCanvas/Package.swift index 7fc45ad1b..ca6d7357f 100644 --- a/Examples/OffscrenCanvas/Package.swift +++ b/Examples/OffscrenCanvas/Package.swift @@ -6,7 +6,7 @@ let package = Package( name: "Example", platforms: [.macOS("15"), .iOS("18"), .watchOS("11"), .tvOS("18"), .visionOS("2")], dependencies: [ - .package(path: "../../"), + .package(path: "../../") ], targets: [ .executableTarget( @@ -15,6 +15,6 @@ let package = Package( .product(name: "JavaScriptKit", package: "JavaScriptKit"), .product(name: "JavaScriptEventLoop", package: "JavaScriptKit"), ] - ), + ) ] ) diff --git a/Examples/OffscrenCanvas/Sources/MyApp/main.swift b/Examples/OffscrenCanvas/Sources/MyApp/main.swift index 67e087122..a2a6e2aac 100644 --- a/Examples/OffscrenCanvas/Sources/MyApp/main.swift +++ b/Examples/OffscrenCanvas/Sources/MyApp/main.swift @@ -56,7 +56,8 @@ func startFPSMonitor() { JSClosure { _ in countFrame() return .undefined - }) + } + ) } // Start counting @@ -107,14 +108,16 @@ func main() async throws { try await onClick(renderer: renderer) } return JSValue.undefined - }) + } + ) _ = cancelButtonElement.addEventListener!( "click", JSClosure { _ in renderingTask?.cancel() return JSValue.undefined - }) + } + ) } Task { @@ -122,18 +125,18 @@ Task { } #if canImport(wasi_pthread) - import wasi_pthread - import WASILibc - - /// Trick to avoid blocking the main thread. pthread_mutex_lock function is used by - /// the Swift concurrency runtime. - @_cdecl("pthread_mutex_lock") - func pthread_mutex_lock(_ mutex: UnsafeMutablePointer) -> Int32 { - // DO NOT BLOCK MAIN THREAD - var ret: Int32 - repeat { - ret = pthread_mutex_trylock(mutex) - } while ret == EBUSY - return ret - } +import wasi_pthread +import WASILibc + +/// Trick to avoid blocking the main thread. pthread_mutex_lock function is used by +/// the Swift concurrency runtime. +@_cdecl("pthread_mutex_lock") +func pthread_mutex_lock(_ mutex: UnsafeMutablePointer) -> Int32 { + // DO NOT BLOCK MAIN THREAD + var ret: Int32 + repeat { + ret = pthread_mutex_trylock(mutex) + } while ret == EBUSY + return ret +} #endif diff --git a/Examples/OffscrenCanvas/Sources/MyApp/render.swift b/Examples/OffscrenCanvas/Sources/MyApp/render.swift index 714cac184..6a9d057a9 100644 --- a/Examples/OffscrenCanvas/Sources/MyApp/render.swift +++ b/Examples/OffscrenCanvas/Sources/MyApp/render.swift @@ -8,12 +8,17 @@ func sleepOnThread(milliseconds: Int, isolation: isolated (any Actor)? = #isolat JSOneshotClosure { _ in continuation.resume() return JSValue.undefined - }, milliseconds + }, + milliseconds ) } } -func renderAnimation(canvas: JSObject, size: Int, isolation: isolated (any Actor)? = #isolation) +func renderAnimation( + canvas: JSObject, + size: Int, + isolation: isolated (any Actor)? = #isolation +) async throws { let ctx = canvas.getContext!("2d").object! diff --git a/Examples/Testing/Package.swift b/Examples/Testing/Package.swift index d9d1719f0..2a81bfa7a 100644 --- a/Examples/Testing/Package.swift +++ b/Examples/Testing/Package.swift @@ -7,7 +7,8 @@ let package = Package( products: [ .library( name: "Counter", - targets: ["Counter"]), + targets: ["Counter"] + ) ], dependencies: [.package(name: "JavaScriptKit", path: "../../")], targets: [ @@ -15,13 +16,14 @@ let package = Package( name: "Counter", dependencies: [ .product(name: "JavaScriptKit", package: "JavaScriptKit") - ]), + ] + ), .testTarget( name: "CounterTests", dependencies: [ "Counter", // This is needed to run the tests in the JavaScript event loop - .product(name: "JavaScriptEventLoopTestSupport", package: "JavaScriptKit") + .product(name: "JavaScriptEventLoopTestSupport", package: "JavaScriptKit"), ] ), ] diff --git a/Examples/Testing/Tests/CounterTests/CounterTests.swift b/Examples/Testing/Tests/CounterTests/CounterTests.swift index 4421c1223..ec92cd14c 100644 --- a/Examples/Testing/Tests/CounterTests/CounterTests.swift +++ b/Examples/Testing/Tests/CounterTests/CounterTests.swift @@ -1,3 +1,5 @@ +import XCTest + @testable import Counter #if canImport(Testing) @@ -18,8 +20,6 @@ import Testing #endif -import XCTest - class CounterTests: XCTestCase { func testIncrement() async { var counter = Counter() diff --git a/IntegrationTests/TestSuites/Package.swift b/IntegrationTests/TestSuites/Package.swift index 3d583d082..1ae22dfa5 100644 --- a/IntegrationTests/TestSuites/Package.swift +++ b/IntegrationTests/TestSuites/Package.swift @@ -8,12 +8,13 @@ let package = Package( // This package doesn't work on macOS host, but should be able to be built for it // for developing on Xcode. This minimum version requirement is to prevent availability // errors for Concurrency API, whose runtime support is shipped from macOS 12.0 - .macOS("12.0"), + .macOS("12.0") ], products: [ .executable( - name: "BenchmarkTests", targets: ["BenchmarkTests"] - ), + name: "BenchmarkTests", + targets: ["BenchmarkTests"] + ) ], dependencies: [.package(name: "JavaScriptKit", path: "../../")], targets: [ diff --git a/IntegrationTests/TestSuites/Sources/BenchmarkTests/main.swift b/IntegrationTests/TestSuites/Sources/BenchmarkTests/main.swift index 2803f0137..6bd10835b 100644 --- a/IntegrationTests/TestSuites/Sources/BenchmarkTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/BenchmarkTests/main.swift @@ -1,24 +1,24 @@ -import JavaScriptKit import CHelpers +import JavaScriptKit let serialization = Benchmark("Serialization") let noopFunction = JSObject.global.noopFunction.function! serialization.testSuite("JavaScript function call through Wasm import") { n in - for _ in 0 ..< n { + for _ in 0.. Void = { _, _ in } ) -> TaskKey { let taskKey = TaskKey(id: output.description) @@ -118,12 +121,20 @@ struct MiniMake { return try encoder.encode($0) } let info = TaskInfo( - wants: inputTasks, inputs: inputFiles, output: output, attributes: attributes, + wants: inputTasks, + inputs: inputFiles, + output: output, + attributes: attributes, salt: saltData ) self.tasks[taskKey] = Task( - info: info, wants: Set(inputTasks), attributes: Set(attributes), - key: taskKey, build: build, isDone: false) + info: info, + wants: Set(inputTasks), + attributes: Set(attributes), + key: taskKey, + build: build, + isDone: false + ) return taskKey } @@ -234,7 +245,9 @@ struct MiniMake { // Ignore directory modification times var isDirectory: ObjCBool = false let fileExists = FileManager.default.fileExists( - atPath: inputURL.path, isDirectory: &isDirectory) + atPath: inputURL.path, + isDirectory: &isDirectory + ) if fileExists && isDirectory.boolValue { return false } diff --git a/Plugins/PackageToJS/Sources/PackageToJS.swift b/Plugins/PackageToJS/Sources/PackageToJS.swift index 80ad9b805..5f66a28ab 100644 --- a/Plugins/PackageToJS/Sources/PackageToJS.swift +++ b/Plugins/PackageToJS/Sources/PackageToJS.swift @@ -73,7 +73,10 @@ struct PackageToJS { testLibraryArguments.append("--list-tests") } if let prelude = testOptions.prelude { - let preludeURL = URL(fileURLWithPath: prelude, relativeTo: URL(fileURLWithPath: FileManager.default.currentDirectoryPath)) + let preludeURL = URL( + fileURLWithPath: prelude, + relativeTo: URL(fileURLWithPath: FileManager.default.currentDirectoryPath) + ) testJsArguments.append("--prelude") testJsArguments.append(preludeURL.path) } @@ -97,9 +100,11 @@ struct PackageToJS { extraArguments.append(contentsOf: testOptions.filter) try PackageToJS.runSingleTestingLibrary( - testRunner: testRunner, currentDirectoryURL: currentDirectoryURL, + testRunner: testRunner, + currentDirectoryURL: currentDirectoryURL, extraArguments: extraArguments, - testParser: testOptions.packageOptions.verbose ? nil : FancyTestsParser(write: { print($0, terminator: "") }), + testParser: testOptions.packageOptions.verbose + ? nil : FancyTestsParser(write: { print($0, terminator: "") }), testOptions: testOptions ) } @@ -120,14 +125,17 @@ struct PackageToJS { } try PackageToJS.runSingleTestingLibrary( - testRunner: testRunner, currentDirectoryURL: currentDirectoryURL, + testRunner: testRunner, + currentDirectoryURL: currentDirectoryURL, extraArguments: extraArguments, testOptions: testOptions ) } if testOptions.packageOptions.enableCodeCoverage { - let profrawFiles = [xctestCoverageFile.path, swiftTestingCoverageFile.path].filter { FileManager.default.fileExists(atPath: $0) } + let profrawFiles = [xctestCoverageFile.path, swiftTestingCoverageFile.path].filter { + FileManager.default.fileExists(atPath: $0) + } do { try PackageToJS.postProcessCoverageFiles(outputDir: outputDir, profrawFiles: profrawFiles) } catch { @@ -254,7 +262,9 @@ extension PackagingSystem { func createDirectory(atPath: String) throws { guard !FileManager.default.fileExists(atPath: atPath) else { return } try FileManager.default.createDirectory( - atPath: atPath, withIntermediateDirectories: true, attributes: nil + atPath: atPath, + withIntermediateDirectories: true, + attributes: nil ) } @@ -264,7 +274,8 @@ extension PackagingSystem { } try FileManager.default.copyItem(atPath: from, toPath: to) try FileManager.default.setAttributes( - [.modificationDate: Date()], ofItemAtPath: to + [.modificationDate: Date()], + ofItemAtPath: to ) } @@ -316,9 +327,9 @@ internal func which(_ executable: String) throws -> URL { } let pathSeparator: Character #if os(Windows) - pathSeparator = ";" + pathSeparator = ";" #else - pathSeparator = ":" + pathSeparator = ":" #endif let paths = ProcessInfo.processInfo.environment["PATH"]!.split(separator: pathSeparator) for path in paths { @@ -401,10 +412,14 @@ struct PackagingPlanner { buildOptions: PackageToJS.BuildOptions ) throws -> MiniMake.TaskKey { let (allTasks, _, _, _) = try planBuildInternal( - make: &make, noOptimize: buildOptions.noOptimize, debugInfoFormat: buildOptions.debugInfoFormat + make: &make, + noOptimize: buildOptions.noOptimize, + debugInfoFormat: buildOptions.debugInfoFormat ) return make.addTask( - inputTasks: allTasks, output: BuildPath(phony: "all"), attributes: [.phony, .silent] + inputTasks: allTasks, + output: BuildPath(phony: "all"), + attributes: [.phony, .silent] ) } @@ -420,7 +435,9 @@ struct PackagingPlanner { ) { // Prepare output directory let outputDirTask = make.addTask( - inputFiles: [selfPath], output: outputDir, attributes: [.silent] + inputFiles: [selfPath], + output: outputDir, + attributes: [.silent] ) { try system.createDirectory(atPath: $1.resolve(path: $0.output).path) } @@ -438,7 +455,9 @@ struct PackagingPlanner { } let intermediatesDirTask = make.addTask( - inputFiles: [selfPath], output: intermediatesDir, attributes: [.silent] + inputFiles: [selfPath], + output: intermediatesDir, + attributes: [.silent] ) { try system.createDirectory(atPath: $1.resolve(path: $0.output).path) } @@ -458,35 +477,50 @@ struct PackagingPlanner { wasmOptInputFile = intermediatesDir.appending(path: wasmFilename + ".no-dwarf") // First, strip DWARF sections as their existence enables DWARF preserving mode in wasm-opt wasmOptInputTask = make.addTask( - inputFiles: [selfPath, wasmProductArtifact], inputTasks: [outputDirTask, intermediatesDirTask], + inputFiles: [selfPath, wasmProductArtifact], + inputTasks: [outputDirTask, intermediatesDirTask], output: wasmOptInputFile ) { print("Stripping DWARF debug info...") - try system.wasmOpt(["--strip-dwarf", "--debuginfo"], input: $1.resolve(path: wasmProductArtifact).path, output: $1.resolve(path: $0.output).path) + try system.wasmOpt( + ["--strip-dwarf", "--debuginfo"], + input: $1.resolve(path: wasmProductArtifact).path, + output: $1.resolve(path: $0.output).path + ) } } // Then, run wasm-opt with all optimizations wasm = make.addTask( - inputFiles: [selfPath, wasmOptInputFile], inputTasks: [outputDirTask] + (wasmOptInputTask.map { [$0] } ?? []), + inputFiles: [selfPath, wasmOptInputFile], + inputTasks: [outputDirTask] + (wasmOptInputTask.map { [$0] } ?? []), output: finalWasmPath ) { print("Optimizing the wasm file...") - try system.wasmOpt(["-Os"] + (debugInfoFormat != .none ? ["--debuginfo"] : []), input: $1.resolve(path: wasmOptInputFile).path, output: $1.resolve(path: $0.output).path) + try system.wasmOpt( + ["-Os"] + (debugInfoFormat != .none ? ["--debuginfo"] : []), + input: $1.resolve(path: wasmOptInputFile).path, + output: $1.resolve(path: $0.output).path + ) } } else { // Copy the wasm product artifact wasm = make.addTask( - inputFiles: [selfPath, wasmProductArtifact], inputTasks: [outputDirTask], + inputFiles: [selfPath, wasmProductArtifact], + inputTasks: [outputDirTask], output: finalWasmPath ) { - try system.syncFile(from: $1.resolve(path: wasmProductArtifact).path, to: $1.resolve(path: $0.output).path) + try system.syncFile( + from: $1.resolve(path: wasmProductArtifact).path, + to: $1.resolve(path: $0.output).path + ) } } packageInputs.append(wasm) let wasmImportsPath = intermediatesDir.appending(path: "wasm-imports.json") let wasmImportsTask = make.addTask( - inputFiles: [selfPath, finalWasmPath], inputTasks: [outputDirTask, intermediatesDirTask, wasm], + inputFiles: [selfPath, finalWasmPath], + inputTasks: [outputDirTask, intermediatesDirTask, wasm], output: wasmImportsPath ) { let metadata = try parseImports( @@ -502,14 +536,20 @@ struct PackagingPlanner { let platformsDir = outputDir.appending(path: "platforms") let platformsDirTask = make.addTask( - inputFiles: [selfPath], output: platformsDir, attributes: [.silent] + inputFiles: [selfPath], + output: platformsDir, + attributes: [.silent] ) { try system.createDirectory(atPath: $1.resolve(path: $0.output).path) } let packageJsonTask = planCopyTemplateFile( - make: &make, file: "Plugins/PackageToJS/Templates/package.json", output: "package.json", outputDirTask: outputDirTask, - inputFiles: [], inputTasks: [] + make: &make, + file: "Plugins/PackageToJS/Templates/package.json", + output: "package.json", + outputDirTask: outputDirTask, + inputFiles: [], + inputTasks: [] ) packageInputs.append(packageJsonTask) @@ -526,11 +566,17 @@ struct PackagingPlanner { ("Plugins/PackageToJS/Templates/platforms/node.d.ts", "platforms/node.d.ts"), ("Sources/JavaScriptKit/Runtime/index.mjs", "runtime.js"), ] { - packageInputs.append(planCopyTemplateFile( - make: &make, file: file, output: output, outputDirTask: outputDirTask, - inputFiles: [wasmImportsPath], inputTasks: [platformsDirTask, wasmImportsTask], - wasmImportsPath: wasmImportsPath - )) + packageInputs.append( + planCopyTemplateFile( + make: &make, + file: file, + output: output, + outputDirTask: outputDirTask, + inputFiles: [wasmImportsPath], + inputTasks: [platformsDirTask, wasmImportsTask], + wasmImportsPath: wasmImportsPath + ) + ) } return (packageInputs, outputDirTask, intermediatesDirTask, packageJsonTask) } @@ -540,24 +586,30 @@ struct PackagingPlanner { make: inout MiniMake ) throws -> (rootTask: MiniMake.TaskKey, binDir: BuildPath) { var (allTasks, outputDirTask, intermediatesDirTask, packageJsonTask) = try planBuildInternal( - make: &make, noOptimize: false, debugInfoFormat: .dwarf + make: &make, + noOptimize: false, + debugInfoFormat: .dwarf ) // Install npm dependencies used in the test harness - allTasks.append(make.addTask( - inputFiles: [ - selfPath, - outputDir.appending(path: "package.json"), - ], inputTasks: [intermediatesDirTask, packageJsonTask], - output: intermediatesDir.appending(path: "npm-install.stamp") - ) { - try system.npmInstall(packageDir: $1.resolve(path: outputDir).path) - try system.writeFile(atPath: $1.resolve(path: $0.output).path, content: Data()) - }) + allTasks.append( + make.addTask( + inputFiles: [ + selfPath, + outputDir.appending(path: "package.json"), + ], + inputTasks: [intermediatesDirTask, packageJsonTask], + output: intermediatesDir.appending(path: "npm-install.stamp") + ) { + try system.npmInstall(packageDir: $1.resolve(path: outputDir).path) + try system.writeFile(atPath: $1.resolve(path: $0.output).path, content: Data()) + } + ) let binDir = outputDir.appending(path: "bin") let binDirTask = make.addTask( - inputFiles: [selfPath], inputTasks: [outputDirTask], + inputFiles: [selfPath], + inputTasks: [outputDirTask], output: binDir ) { try system.createDirectory(atPath: $1.resolve(path: $0.output).path) @@ -571,13 +623,21 @@ struct PackagingPlanner { ("Plugins/PackageToJS/Templates/test.browser.html", "test.browser.html"), ("Plugins/PackageToJS/Templates/bin/test.js", "bin/test.js"), ] { - allTasks.append(planCopyTemplateFile( - make: &make, file: file, output: output, outputDirTask: outputDirTask, - inputFiles: [], inputTasks: [binDirTask] - )) + allTasks.append( + planCopyTemplateFile( + make: &make, + file: file, + output: output, + outputDirTask: outputDirTask, + inputFiles: [], + inputTasks: [binDirTask] + ) + ) } let rootTask = make.addTask( - inputTasks: allTasks, output: BuildPath(phony: "all"), attributes: [.phony, .silent] + inputTasks: allTasks, + output: BuildPath(phony: "all"), + attributes: [.phony, .silent] ) return (rootTask, binDir) } @@ -610,14 +670,19 @@ struct PackagingPlanner { let salt = Salt(conditions: conditions, substitutions: constantSubstitutions) return make.addTask( - inputFiles: [selfPath, inputPath] + inputFiles, inputTasks: [outputDirTask] + inputTasks, - output: outputDir.appending(path: output), salt: salt + inputFiles: [selfPath, inputPath] + inputFiles, + inputTasks: [outputDirTask] + inputTasks, + output: outputDir.appending(path: output), + salt: salt ) { var substitutions = constantSubstitutions if let wasmImportsPath = wasmImportsPath { let wasmImportsPath = $1.resolve(path: wasmImportsPath) - let importEntries = try JSONDecoder().decode([ImportEntry].self, from: Data(contentsOf: wasmImportsPath)) + let importEntries = try JSONDecoder().decode( + [ImportEntry].self, + from: Data(contentsOf: wasmImportsPath) + ) let memoryImport = importEntries.first { $0.module == "env" && $0.name == "memory" } diff --git a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift index 62e7dc16e..5f257079a 100644 --- a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift +++ b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift @@ -20,8 +20,8 @@ struct PackageToJSPlugin: CommandPlugin { // In case user misses the `--swift-sdk` option { build, arguments in guard - build.logText.contains("ld.gold: --export-if-defined=__main_argc_argv: unknown option") || - build.logText.contains("-static-stdlib is no longer supported for Apple platforms") + build.logText.contains("ld.gold: --export-if-defined=__main_argc_argv: unknown option") + || build.logText.contains("-static-stdlib is no longer supported for Apple platforms") else { return nil } let didYouMean = [ @@ -57,7 +57,11 @@ struct PackageToJSPlugin: CommandPlugin { ( // In case selected toolchain is a Xcode toolchain, not OSS toolchain { build, arguments in - guard build.logText.contains("No available targets are compatible with triple \"wasm32-unknown-wasi\"") else { + guard + build.logText.contains( + "No available targets are compatible with triple \"wasm32-unknown-wasi\"" + ) + else { return nil } return """ @@ -74,7 +78,8 @@ struct PackageToJSPlugin: CommandPlugin { } private func reportBuildFailure( - _ build: PackageManager.BuildResult, _ arguments: [String] + _ build: PackageManager.BuildResult, + _ arguments: [String] ) { for diagnostic in Self.friendlyBuildDiagnostics { if let message = diagnostic(build, arguments) { @@ -100,11 +105,12 @@ struct PackageToJSPlugin: CommandPlugin { if filePath.hasPrefix(packageDir.path) { // Emit hint for --allow-writing-to-package-directory if the destination path // is under the package directory - let didYouMean = [ - "swift", "package", "--swift-sdk", "wasm32-unknown-wasi", - "plugin", "--allow-writing-to-package-directory", - "js", - ] + arguments + let didYouMean = + [ + "swift", "package", "--swift-sdk", "wasm32-unknown-wasi", + "plugin", "--allow-writing-to-package-directory", + "js", + ] + arguments emitHintMessage( """ Please pass `--allow-writing-to-package-directory` to "swift package". @@ -116,11 +122,12 @@ struct PackageToJSPlugin: CommandPlugin { } else { // Emit hint for --allow-writing-to-directory // if the destination path is outside the package directory - let didYouMean = [ - "swift", "package", "--swift-sdk", "wasm32-unknown-wasi", - "plugin", "--allow-writing-to-directory", "\(filePath)", - "js", - ] + arguments + let didYouMean = + [ + "swift", "package", "--swift-sdk", "wasm32-unknown-wasi", + "plugin", "--allow-writing-to-directory", "\(filePath)", + "js", + ] + arguments emitHintMessage( """ Please pass `--allow-writing-to-directory ` to "swift package". @@ -147,7 +154,8 @@ struct PackageToJSPlugin: CommandPlugin { if extractor.remainingArguments.count > 0 { printStderr( - "Unexpected arguments: \(extractor.remainingArguments.joined(separator: " "))") + "Unexpected arguments: \(extractor.remainingArguments.joined(separator: " "))" + ) printStderr(PackageToJS.BuildOptions.help()) exit(1) } @@ -155,7 +163,8 @@ struct PackageToJSPlugin: CommandPlugin { // Build products let productName = try buildOptions.product ?? deriveDefaultProduct(package: context.package) let build = try buildWasm( - productName: productName, context: context, + productName: productName, + context: context, options: buildOptions.packageOptions ) guard build.succeeded else { @@ -171,7 +180,9 @@ struct PackageToJSPlugin: CommandPlugin { } guard let selfPackage = findPackageInDependencies( - package: context.package, id: Self.JAVASCRIPTKIT_PACKAGE_ID) + package: context.package, + id: Self.JAVASCRIPTKIT_PACKAGE_ID + ) else { throw PackageToJSError("Failed to find JavaScriptKit in dependencies!?") } @@ -180,12 +191,17 @@ struct PackageToJSPlugin: CommandPlugin { printProgress: self.printProgress ) let planner = PackagingPlanner( - options: buildOptions.packageOptions, context: context, selfPackage: selfPackage, - outputDir: outputDir, wasmProductArtifact: productArtifact, + options: buildOptions.packageOptions, + context: context, + selfPackage: selfPackage, + outputDir: outputDir, + wasmProductArtifact: productArtifact, wasmFilename: productArtifact.lastPathComponent ) let rootTask = try planner.planBuild( - make: &make, buildOptions: buildOptions) + make: &make, + buildOptions: buildOptions + ) cleanIfBuildGraphChanged(root: rootTask, make: make, context: context) print("Packaging...") let scope = MiniMake.VariableScope(variables: [:]) @@ -204,14 +220,16 @@ struct PackageToJSPlugin: CommandPlugin { if extractor.remainingArguments.count > 0 { printStderr( - "Unexpected arguments: \(extractor.remainingArguments.joined(separator: " "))") + "Unexpected arguments: \(extractor.remainingArguments.joined(separator: " "))" + ) printStderr(PackageToJS.TestOptions.help()) exit(1) } let productName = "\(context.package.displayName)PackageTests" let build = try buildWasm( - productName: productName, context: context, + productName: productName, + context: context, options: testOptions.packageOptions ) guard build.succeeded else { @@ -237,7 +255,8 @@ struct PackageToJSPlugin: CommandPlugin { } guard let productArtifact = productArtifact else { throw PackageToJSError( - "Failed to find '\(productName).wasm' or '\(productName).xctest'") + "Failed to find '\(productName).wasm' or '\(productName).xctest'" + ) } let outputDir = if let outputPath = testOptions.packageOptions.outputPath { @@ -247,7 +266,9 @@ struct PackageToJSPlugin: CommandPlugin { } guard let selfPackage = findPackageInDependencies( - package: context.package, id: Self.JAVASCRIPTKIT_PACKAGE_ID) + package: context.package, + id: Self.JAVASCRIPTKIT_PACKAGE_ID + ) else { throw PackageToJSError("Failed to find JavaScriptKit in dependencies!?") } @@ -256,8 +277,11 @@ struct PackageToJSPlugin: CommandPlugin { printProgress: self.printProgress ) let planner = PackagingPlanner( - options: testOptions.packageOptions, context: context, selfPackage: selfPackage, - outputDir: outputDir, wasmProductArtifact: productArtifact, + options: testOptions.packageOptions, + context: context, + selfPackage: selfPackage, + outputDir: outputDir, + wasmProductArtifact: productArtifact, // If the product artifact doesn't have a .wasm extension, add it // to deliver it with the correct MIME type when serving the test // files for browser tests. @@ -266,7 +290,8 @@ struct PackageToJSPlugin: CommandPlugin { : productArtifact.lastPathComponent + ".wasm" ) let (rootTask, binDir) = try planner.planTestBuild( - make: &make) + make: &make + ) cleanIfBuildGraphChanged(root: rootTask, make: make, context: context) print("Packaging tests...") let scope = MiniMake.VariableScope(variables: [:]) @@ -284,7 +309,11 @@ struct PackageToJSPlugin: CommandPlugin { } } - private func buildWasm(productName: String, context: PluginContext, options: PackageToJS.PackageOptions) throws + private func buildWasm( + productName: String, + context: PluginContext, + options: PackageToJS.PackageOptions + ) throws -> PackageManager.BuildResult { var parameters = PackageManager.BuildParameters( @@ -295,7 +324,8 @@ struct PackageToJSPlugin: CommandPlugin { parameters.otherSwiftcFlags = ["-color-diagnostics"] let buildingForEmbedded = ProcessInfo.processInfo.environment["JAVASCRIPTKIT_EXPERIMENTAL_EMBEDDED_WASM"].flatMap( - Bool.init) ?? false + Bool.init + ) ?? false if !buildingForEmbedded { // NOTE: We only support static linking for now, and the new SwiftDriver // does not infer `-static-stdlib` for WebAssembly targets intentionally @@ -323,7 +353,8 @@ struct PackageToJSPlugin: CommandPlugin { /// path. private func cleanIfBuildGraphChanged( root: MiniMake.TaskKey, - make: MiniMake, context: PluginContext + make: MiniMake, + context: PluginContext ) { let buildFingerprint = context.pluginWorkDirectoryURL.appending(path: "minimake.json") let lastBuildFingerprint = try? Data(contentsOf: buildFingerprint) @@ -338,7 +369,8 @@ struct PackageToJSPlugin: CommandPlugin { private func printProgress(context: MiniMake.ProgressPrinter.Context, message: String) { let buildCwd = FileManager.default.currentDirectoryPath let outputPath = context.scope.resolve(path: context.subject.output).path - let displayName = outputPath.hasPrefix(buildCwd + "/") + let displayName = + outputPath.hasPrefix(buildCwd + "/") ? String(outputPath.dropFirst(buildCwd.count + 1)) : outputPath printStderr("[\(context.built + 1)/\(context.total)] \(displayName): \(message)") } @@ -359,7 +391,12 @@ extension PackageToJS.PackageOptions { let verbose = extractor.extractFlag(named: "verbose") let enableCodeCoverage = extractor.extractFlag(named: "enable-code-coverage") return PackageToJS.PackageOptions( - outputPath: outputPath, packageName: packageName, explain: explain != 0, verbose: verbose != 0, useCDN: useCDN != 0, enableCodeCoverage: enableCodeCoverage != 0 + outputPath: outputPath, + packageName: packageName, + explain: explain != 0, + verbose: verbose != 0, + useCDN: useCDN != 0, + enableCodeCoverage: enableCodeCoverage != 0 ) } } @@ -372,12 +409,19 @@ extension PackageToJS.BuildOptions { var debugInfoFormat: PackageToJS.DebugInfoFormat = .none if let rawDebugInfoFormat = rawDebugInfoFormat { guard let format = PackageToJS.DebugInfoFormat(rawValue: rawDebugInfoFormat) else { - fatalError("Invalid debug info format: \(rawDebugInfoFormat), expected one of \(PackageToJS.DebugInfoFormat.allCases.map(\.rawValue).joined(separator: ", "))") + fatalError( + "Invalid debug info format: \(rawDebugInfoFormat), expected one of \(PackageToJS.DebugInfoFormat.allCases.map(\.rawValue).joined(separator: ", "))" + ) } debugInfoFormat = format } let packageOptions = PackageToJS.PackageOptions.parse(from: &extractor) - return PackageToJS.BuildOptions(product: product, noOptimize: noOptimize != 0, debugInfoFormat: debugInfoFormat, packageOptions: packageOptions) + return PackageToJS.BuildOptions( + product: product, + noOptimize: noOptimize != 0, + debugInfoFormat: debugInfoFormat, + packageOptions: packageOptions + ) } static func help() -> String { @@ -424,8 +468,12 @@ extension PackageToJS.TestOptions { let extraNodeArguments = extractor.extractSingleDashOption(named: "Xnode") let packageOptions = PackageToJS.PackageOptions.parse(from: &extractor) var options = PackageToJS.TestOptions( - buildOnly: buildOnly != 0, listTests: listTests != 0, - filter: filter, prelude: prelude, environment: environment, inspect: inspect != 0, + buildOnly: buildOnly != 0, + listTests: listTests != 0, + filter: filter, + prelude: prelude, + environment: environment, + inspect: inspect != 0, extraNodeArguments: extraNodeArguments, packageOptions: packageOptions ) @@ -467,36 +515,34 @@ extension PackageToJS.TestOptions { // MARK: - PackagePlugin helpers extension ArgumentExtractor { - fileprivate mutating func extractSingleDashOption(named name: String) -> [String] { - let parts = remainingArguments.split(separator: "--", maxSplits: 1, omittingEmptySubsequences: false) - var args = Array(parts[0]) - let literals = Array(parts.count == 2 ? parts[1] : []) - - var values: [String] = [] - var idx = 0 - while idx < args.count { - var arg = args[idx] - if arg == "-\(name)" { - args.remove(at: idx) - if idx < args.count { - let val = args[idx] - values.append(val) - args.remove(at: idx) + fileprivate mutating func extractSingleDashOption(named name: String) -> [String] { + let parts = remainingArguments.split(separator: "--", maxSplits: 1, omittingEmptySubsequences: false) + var args = Array(parts[0]) + let literals = Array(parts.count == 2 ? parts[1] : []) + + var values: [String] = [] + var idx = 0 + while idx < args.count { + var arg = args[idx] + if arg == "-\(name)" { + args.remove(at: idx) + if idx < args.count { + let val = args[idx] + values.append(val) + args.remove(at: idx) + } + } else if arg.starts(with: "-\(name)=") { + args.remove(at: idx) + arg.removeFirst(2 + name.count) + values.append(arg) + } else { + idx += 1 + } } - } - else if arg.starts(with: "-\(name)=") { - args.remove(at: idx) - arg.removeFirst(2 + name.count) - values.append(arg) - } - else { - idx += 1 - } - } - self = ArgumentExtractor(args + literals) - return values - } + self = ArgumentExtractor(args + literals) + return values + } } /// Derive default product from the package @@ -506,7 +552,8 @@ internal func deriveDefaultProduct(package: Package) throws -> String { let executableProducts = package.products(ofType: ExecutableProduct.self) guard !executableProducts.isEmpty else { throw PackageToJSError( - "Make sure there's at least one executable product in your Package.swift") + "Make sure there's at least one executable product in your Package.swift" + ) } guard executableProducts.count == 1 else { throw PackageToJSError( @@ -568,7 +615,9 @@ extension PackagingPlanner { self.init( options: options, packageId: context.package.id, - intermediatesDir: BuildPath(absolute: context.pluginWorkDirectoryURL.appending(path: outputBaseName + ".tmp").path), + intermediatesDir: BuildPath( + absolute: context.pluginWorkDirectoryURL.appending(path: outputBaseName + ".tmp").path + ), selfPackageDir: BuildPath(absolute: selfPackage.directoryURL.path), outputDir: BuildPath(absolute: outputDir.path), wasmProductArtifact: BuildPath(absolute: wasmProductArtifact.path), diff --git a/Plugins/PackageToJS/Sources/ParseWasm.swift b/Plugins/PackageToJS/Sources/ParseWasm.swift index 8cfb6c66c..4372b32c5 100644 --- a/Plugins/PackageToJS/Sources/ParseWasm.swift +++ b/Plugins/PackageToJS/Sources/ParseWasm.swift @@ -199,7 +199,8 @@ func parseImports(moduleBytes: Data) throws -> [ImportEntry] { case 0x02: // Memory let limits = try parseLimits(parseState) imports.append( - ImportEntry(module: module, name: name, kind: .memory(type: limits))) + ImportEntry(module: module, name: name, kind: .memory(type: limits)) + ) case 0x03: // Global _ = try parseGlobalType(parseState) diff --git a/Plugins/PackageToJS/Sources/Preprocess.swift b/Plugins/PackageToJS/Sources/Preprocess.swift index 835dd31a6..bafa2aae5 100644 --- a/Plugins/PackageToJS/Sources/Preprocess.swift +++ b/Plugins/PackageToJS/Sources/Preprocess.swift @@ -66,7 +66,10 @@ private struct Preprocessor { } /// Get the 1-indexed line and column - private static func computeLineAndColumn(from index: String.Index, in source: String) -> (line: Int, column: Int) { + private static func computeLineAndColumn( + from index: String.Index, + in source: String + ) -> (line: Int, column: Int) { var line = 1 var column = 1 for char in source[.. PreprocessorError { + func unexpectedCharacterError( + expected: CustomStringConvertible, + character: Character, + at index: String.Index + ) -> PreprocessorError { return PreprocessorError( file: file, - message: "Expected \(expected) but got \(character)", source: source, index: index) + message: "Expected \(expected) but got \(character)", + source: source, + index: index + ) } func unexpectedDirectiveError(at index: String.Index) -> PreprocessorError { return PreprocessorError( file: file, - message: "Unexpected directive", source: source, index: index) + message: "Unexpected directive", + source: source, + index: index + ) } func eofError(at index: String.Index) -> PreprocessorError { return PreprocessorError( file: file, - message: "Unexpected end of input", source: source, index: index) + message: "Unexpected end of input", + source: source, + index: index + ) } func undefinedVariableError(name: String, at index: String.Index) -> PreprocessorError { return PreprocessorError( file: file, - message: "Undefined variable \(name)", source: source, index: index) + message: "Undefined variable \(name)", + source: source, + index: index + ) } func tokenize() throws -> [TokenInfo] { @@ -188,7 +210,10 @@ private struct Preprocessor { func expect(_ expected: String) throws { guard let endIndex = source.index( - cursor, offsetBy: expected.count, limitedBy: source.endIndex) + cursor, + offsetBy: expected.count, + limitedBy: source.endIndex + ) else { throw eofError(at: cursor) } @@ -281,7 +306,11 @@ private struct Preprocessor { enum ParseResult { case block(String) indirect case `if`( - condition: String, then: [ParseResult], else: [ParseResult], position: String.Index) + condition: String, + then: [ParseResult], + else: [ParseResult], + position: String.Index + ) } func parse(tokens: [TokenInfo]) throws -> [ParseResult] { @@ -314,13 +343,19 @@ private struct Preprocessor { } guard case .endif = tokens[cursor].token else { throw unexpectedTokenError( - expected: .endif, token: tokens[cursor].token, at: tokens[cursor].position) + expected: .endif, + token: tokens[cursor].token, + at: tokens[cursor].position + ) } consume() return .if(condition: condition, then: then, else: `else`, position: ifPosition) case .else, .endif: throw unexpectedTokenError( - expected: nil, token: tokens[cursor].token, at: tokens[cursor].position) + expected: nil, + token: tokens[cursor].token, + at: tokens[cursor].position + ) } } var results: [ParseResult] = [] @@ -338,9 +373,13 @@ private struct Preprocessor { var substitutedContent = content for (key, value) in options.substitutions { substitutedContent = substitutedContent.replacingOccurrences( - of: "@" + key + "@", with: value) + of: "@" + key + "@", + with: value + ) substitutedContent = substitutedContent.replacingOccurrences( - of: "import.meta." + key, with: value) + of: "import.meta." + key, + with: value + ) } result.append(substitutedContent) } diff --git a/Plugins/PackageToJS/Sources/TestsParser.swift b/Plugins/PackageToJS/Sources/TestsParser.swift index 61d417521..72afd6b07 100644 --- a/Plugins/PackageToJS/Sources/TestsParser.swift +++ b/Plugins/PackageToJS/Sources/TestsParser.swift @@ -186,7 +186,8 @@ class FancyTestsParser { write("\n") func formatCategory( - label: String, statuses: [Status] + label: String, + statuses: [Status] ) -> String { var passed = 0 var skipped = 0 @@ -220,7 +221,8 @@ class FancyTestsParser { let suitesWithCases = suites.filter { $0.cases.count > 0 } write( formatCategory( - label: "Test Suites:", statuses: suitesWithCases.map(\.status) + label: "Test Suites:", + statuses: suitesWithCases.map(\.status) ) ) let allCaseStatuses = suitesWithCases.flatMap { @@ -228,7 +230,8 @@ class FancyTestsParser { } write( formatCategory( - label: "Tests: ", statuses: allCaseStatuses + label: "Tests: ", + statuses: allCaseStatuses ) ) diff --git a/Plugins/PackageToJS/Tests/ExampleTests.swift b/Plugins/PackageToJS/Tests/ExampleTests.swift index 508062297..304878692 100644 --- a/Plugins/PackageToJS/Tests/ExampleTests.swift +++ b/Plugins/PackageToJS/Tests/ExampleTests.swift @@ -54,7 +54,10 @@ extension Trait where Self == ConditionTrait { static func copyRepository(to destination: URL) throws { try FileManager.default.createDirectory( - atPath: destination.path, withIntermediateDirectories: true, attributes: nil) + atPath: destination.path, + withIntermediateDirectories: true, + attributes: nil + ) let ignore = [ ".git", ".vscode", @@ -81,7 +84,9 @@ extension Trait where Self == ConditionTrait { do { try FileManager.default.createDirectory( at: destinationPath.deletingLastPathComponent(), - withIntermediateDirectories: true, attributes: nil) + withIntermediateDirectories: true, + attributes: nil + ) try FileManager.default.copyItem(at: sourcePath, to: destinationPath) } catch { print("Failed to copy \(sourcePath) to \(destinationPath): \(error)") @@ -101,7 +106,9 @@ extension Trait where Self == ConditionTrait { process.executableURL = URL( fileURLWithPath: "swift", relativeTo: URL( - fileURLWithPath: try #require(Self.getSwiftPath()))) + fileURLWithPath: try #require(Self.getSwiftPath()) + ) + ) process.arguments = args process.currentDirectoryURL = destination.appending(path: path) process.environment = ProcessInfo.processInfo.environment.merging(env) { _, new in @@ -141,7 +148,10 @@ extension Trait where Self == ConditionTrait { try runSwift(["package", "--swift-sdk", swiftSDKID, "js"], [:]) try runSwift(["package", "--swift-sdk", swiftSDKID, "js", "--debug-info-format", "dwarf"], [:]) try runSwift(["package", "--swift-sdk", swiftSDKID, "js", "--debug-info-format", "name"], [:]) - try runSwift(["package", "--swift-sdk", swiftSDKID, "-Xswiftc", "-DJAVASCRIPTKIT_WITHOUT_WEAKREFS", "js"], [:]) + try runSwift( + ["package", "--swift-sdk", swiftSDKID, "-Xswiftc", "-DJAVASCRIPTKIT_WITHOUT_WEAKREFS", "js"], + [:] + ) } } @@ -152,17 +162,23 @@ extension Trait where Self == ConditionTrait { try runSwift(["package", "--swift-sdk", swiftSDKID, "js", "test"], [:]) try withTemporaryDirectory(body: { tempDir, _ in let scriptContent = """ - const fs = require('fs'); - const path = require('path'); - const scriptPath = path.join(__dirname, 'test.txt'); - fs.writeFileSync(scriptPath, 'Hello, world!'); - """ + const fs = require('fs'); + const path = require('path'); + const scriptPath = path.join(__dirname, 'test.txt'); + fs.writeFileSync(scriptPath, 'Hello, world!'); + """ try scriptContent.write(to: tempDir.appending(path: "script.js"), atomically: true, encoding: .utf8) let scriptPath = tempDir.appending(path: "script.js") - try runSwift(["package", "--swift-sdk", swiftSDKID, "js", "test", "-Xnode=--require=\(scriptPath.path)"], [:]) + try runSwift( + ["package", "--swift-sdk", swiftSDKID, "js", "test", "-Xnode=--require=\(scriptPath.path)"], + [:] + ) let testPath = tempDir.appending(path: "test.txt") try #require(FileManager.default.fileExists(atPath: testPath.path), "test.txt should exist") - try #require(try String(contentsOf: testPath, encoding: .utf8) == "Hello, world!", "test.txt should be created by the script") + try #require( + try String(contentsOf: testPath, encoding: .utf8) == "Hello, world!", + "test.txt should be created by the script" + ) }) try runSwift(["package", "--swift-sdk", swiftSDKID, "js", "test", "--environment", "browser"], [:]) } @@ -174,15 +190,22 @@ extension Trait where Self == ConditionTrait { let swiftSDKID = try #require(Self.getSwiftSDKID()) let swiftPath = try #require(Self.getSwiftPath()) try withPackage(at: "Examples/Testing") { packageDir, runSwift in - try runSwift(["package", "--swift-sdk", swiftSDKID, "js", "test", "--enable-code-coverage"], [ - "LLVM_PROFDATA_PATH": URL(fileURLWithPath: swiftPath).appending(path: "llvm-profdata").path - ]) + try runSwift( + ["package", "--swift-sdk", swiftSDKID, "js", "test", "--enable-code-coverage"], + [ + "LLVM_PROFDATA_PATH": URL(fileURLWithPath: swiftPath).appending(path: "llvm-profdata").path + ] + ) do { let llvmCov = try which("llvm-cov") let process = Process() process.executableURL = llvmCov - let profdata = packageDir.appending(path: ".build/plugins/PackageToJS/outputs/PackageTests/default.profdata") - let wasm = packageDir.appending(path: ".build/plugins/PackageToJS/outputs/PackageTests/TestingPackageTests.wasm") + let profdata = packageDir.appending( + path: ".build/plugins/PackageToJS/outputs/PackageTests/default.profdata" + ) + let wasm = packageDir.appending( + path: ".build/plugins/PackageToJS/outputs/PackageTests/TestingPackageTests.wasm" + ) process.arguments = ["report", "-instr-profile", profdata.path, wasm.path] process.standardOutput = FileHandle.nullDevice try process.run() diff --git a/Plugins/PackageToJS/Tests/MiniMakeTests.swift b/Plugins/PackageToJS/Tests/MiniMakeTests.swift index b15a87607..c0bba29c7 100644 --- a/Plugins/PackageToJS/Tests/MiniMakeTests.swift +++ b/Plugins/PackageToJS/Tests/MiniMakeTests.swift @@ -14,9 +14,12 @@ import Testing try "Hello".write(toFile: $1.resolve(path: $0.output).path, atomically: true, encoding: .utf8) } - try make.build(output: task, scope: MiniMake.VariableScope(variables: [ - "OUTPUT": tempDir.path, - ])) + try make.build( + output: task, + scope: MiniMake.VariableScope(variables: [ + "OUTPUT": tempDir.path + ]) + ) let content = try String(contentsOfFile: tempDir.appendingPathComponent("output.txt").path, encoding: .utf8) #expect(content == "Hello") } @@ -28,7 +31,7 @@ import Testing var make = MiniMake(printProgress: { _, _ in }) let prefix = BuildPath(prefix: "PREFIX") let scope = MiniMake.VariableScope(variables: [ - "PREFIX": tempDir.path, + "PREFIX": tempDir.path ]) let input = prefix.appending(path: "input.txt") let intermediate = prefix.appending(path: "intermediate.txt") @@ -39,15 +42,23 @@ import Testing let intermediateTask = make.addTask(inputFiles: [input], output: intermediate) { task, outputURL in let content = try String(contentsOfFile: scope.resolve(path: task.inputs[0]).path, encoding: .utf8) try (content + " processed").write( - toFile: scope.resolve(path: task.output).path, atomically: true, encoding: .utf8) + toFile: scope.resolve(path: task.output).path, + atomically: true, + encoding: .utf8 + ) } let finalTask = make.addTask( - inputFiles: [intermediate], inputTasks: [intermediateTask], output: output + inputFiles: [intermediate], + inputTasks: [intermediateTask], + output: output ) { task, scope in let content = try String(contentsOfFile: scope.resolve(path: task.inputs[0]).path, encoding: .utf8) try (content + " final").write( - toFile: scope.resolve(path: task.output).path, atomically: true, encoding: .utf8) + toFile: scope.resolve(path: task.output).path, + atomically: true, + encoding: .utf8 + ) } try make.build(output: finalTask, scope: scope) @@ -67,11 +78,15 @@ import Testing let task = make.addTask(output: outputPath, attributes: [.phony]) { task, scope in buildCount += 1 - try String(buildCount).write(toFile: scope.resolve(path: task.output).path, atomically: true, encoding: .utf8) + try String(buildCount).write( + toFile: scope.resolve(path: task.output).path, + atomically: true, + encoding: .utf8 + ) } let scope = MiniMake.VariableScope(variables: [ - "OUTPUT": tempDir.path, + "OUTPUT": tempDir.path ]) try make.build(output: task, scope: scope) try make.build(output: task, scope: scope) @@ -102,7 +117,7 @@ import Testing var make = MiniMake(printProgress: { _, _ in }) let prefix = BuildPath(prefix: "PREFIX") let scope = MiniMake.VariableScope(variables: [ - "PREFIX": tempDir.path, + "PREFIX": tempDir.path ]) let input = prefix.appending(path: "input.txt") let output = prefix.appending(path: "output.txt") @@ -142,7 +157,7 @@ import Testing ) let prefix = BuildPath(prefix: "PREFIX") let scope = MiniMake.VariableScope(variables: [ - "PREFIX": tempDir.path, + "PREFIX": tempDir.path ]) let silentOutputPath = prefix.appending(path: "silent.txt") let silentTask = make.addTask(output: silentOutputPath, attributes: [.silent]) { task, scope in @@ -150,14 +165,21 @@ import Testing } let finalOutputPath = prefix.appending(path: "output.txt") let task = make.addTask( - inputTasks: [silentTask], output: finalOutputPath + inputTasks: [silentTask], + output: finalOutputPath ) { task, scope in try "Hello".write(toFile: scope.resolve(path: task.output).path, atomically: true, encoding: .utf8) } try make.build(output: task, scope: scope) - #expect(FileManager.default.fileExists(atPath: scope.resolve(path: silentOutputPath).path), "Silent task should still create output file") - #expect(FileManager.default.fileExists(atPath: scope.resolve(path: finalOutputPath).path), "Final task should create output file") + #expect( + FileManager.default.fileExists(atPath: scope.resolve(path: silentOutputPath).path), + "Silent task should still create output file" + ) + #expect( + FileManager.default.fileExists(atPath: scope.resolve(path: finalOutputPath).path), + "Final task should create output file" + ) try #require(messages.count == 1, "Should print progress for the final task") #expect(messages[0] == ("$PREFIX/output.txt", 1, 0, "\u{1B}[32mbuilding\u{1B}[0m")) } @@ -170,7 +192,7 @@ import Testing var make = MiniMake(printProgress: { _, _ in }) let prefix = BuildPath(prefix: "PREFIX") let scope = MiniMake.VariableScope(variables: [ - "PREFIX": tempDir.path, + "PREFIX": tempDir.path ]) let output = prefix.appending(path: "error.txt") @@ -190,7 +212,7 @@ import Testing var make = MiniMake(printProgress: { _, _ in }) let prefix = BuildPath(prefix: "PREFIX") let scope = MiniMake.VariableScope(variables: [ - "PREFIX": tempDir.path, + "PREFIX": tempDir.path ]) let outputs = [ prefix.appending(path: "clean1.txt"), @@ -200,7 +222,11 @@ import Testing // Create tasks and build them let tasks = outputs.map { output in make.addTask(output: output) { task, scope in - try "Content".write(toFile: scope.resolve(path: task.output).path, atomically: true, encoding: .utf8) + try "Content".write( + toFile: scope.resolve(path: task.output).path, + atomically: true, + encoding: .utf8 + ) } } @@ -212,7 +238,8 @@ import Testing for output in outputs { #expect( FileManager.default.fileExists(atPath: scope.resolve(path: output).path), - "Output file should exist before cleanup") + "Output file should exist before cleanup" + ) } // Clean everything @@ -222,7 +249,8 @@ import Testing for output in outputs { #expect( !FileManager.default.fileExists(atPath: scope.resolve(path: output).path), - "Output file should not exist after cleanup") + "Output file should not exist after cleanup" + ) } } } diff --git a/Plugins/PackageToJS/Tests/PackagingPlannerTests.swift b/Plugins/PackageToJS/Tests/PackagingPlannerTests.swift index 6392ca664..c69dcb66f 100644 --- a/Plugins/PackageToJS/Tests/PackagingPlannerTests.swift +++ b/Plugins/PackageToJS/Tests/PackagingPlannerTests.swift @@ -15,12 +15,15 @@ import Testing func wasmOpt(_ arguments: [String], input: String, output: String) throws { try FileManager.default.copyItem( - at: URL(fileURLWithPath: input), to: URL(fileURLWithPath: output)) + at: URL(fileURLWithPath: input), + to: URL(fileURLWithPath: output) + ) } } func snapshotBuildPlan( - filePath: String = #filePath, function: String = #function, + filePath: String = #filePath, + function: String = #function, sourceLocation: SourceLocation = #_sourceLocation, variant: String? = nil, body: (inout MiniMake) throws -> MiniMake.TaskKey @@ -29,8 +32,11 @@ import Testing let rootKey = try body(&make) let fingerprint = try make.computeFingerprint(root: rootKey, prettyPrint: true) try assertSnapshot( - filePath: filePath, function: function, sourceLocation: sourceLocation, - variant: variant, input: fingerprint + filePath: filePath, + function: function, + sourceLocation: sourceLocation, + variant: variant, + input: fingerprint ) } @@ -39,11 +45,19 @@ import Testing @Test(arguments: [ (variant: "debug", configuration: "debug", noOptimize: false, debugInfoFormat: DebugInfoFormat.none), (variant: "release", configuration: "release", noOptimize: false, debugInfoFormat: DebugInfoFormat.none), - (variant: "release_no_optimize", configuration: "release", noOptimize: true, debugInfoFormat: DebugInfoFormat.none), + ( + variant: "release_no_optimize", configuration: "release", noOptimize: true, + debugInfoFormat: DebugInfoFormat.none + ), (variant: "release_dwarf", configuration: "release", noOptimize: false, debugInfoFormat: DebugInfoFormat.dwarf), (variant: "release_name", configuration: "release", noOptimize: false, debugInfoFormat: DebugInfoFormat.name), ]) - func planBuild(variant: String, configuration: String, noOptimize: Bool, debugInfoFormat: PackageToJS.DebugInfoFormat) throws { + func planBuild( + variant: String, + configuration: String, + noOptimize: Bool, + debugInfoFormat: PackageToJS.DebugInfoFormat + ) throws { let options = PackageToJS.PackageOptions() let system = TestPackagingSystem() let planner = PackagingPlanner( @@ -88,7 +102,7 @@ import Testing selfPath: BuildPath(prefix: "PLANNER_SOURCE_PATH"), system: system ) - try snapshotBuildPlan() { make in + try snapshotBuildPlan { make in let (root, binDir) = try planner.planTestBuild(make: &make) #expect(binDir.description == "$OUTPUT/bin") return root diff --git a/Plugins/PackageToJS/Tests/PreprocessTests.swift b/Plugins/PackageToJS/Tests/PreprocessTests.swift index 9ebb7a161..6e7e4a1b9 100644 --- a/Plugins/PackageToJS/Tests/PreprocessTests.swift +++ b/Plugins/PackageToJS/Tests/PreprocessTests.swift @@ -1,15 +1,16 @@ import Testing + @testable import PackageToJS @Suite struct PreprocessTests { @Test func thenBlock() throws { let source = """ - /* #if FOO */ - console.log("FOO"); - /* #else */ - console.log("BAR"); - /* #endif */ - """ + /* #if FOO */ + console.log("FOO"); + /* #else */ + console.log("BAR"); + /* #endif */ + """ let options = PreprocessOptions(conditions: ["FOO": true]) let result = try preprocess(source: source, options: options) #expect(result == "console.log(\"FOO\");\n") @@ -17,12 +18,12 @@ import Testing @Test func elseBlock() throws { let source = """ - /* #if FOO */ - console.log("FOO"); - /* #else */ - console.log("BAR"); - /* #endif */ - """ + /* #if FOO */ + console.log("FOO"); + /* #else */ + console.log("BAR"); + /* #endif */ + """ let options = PreprocessOptions(conditions: ["FOO": false]) let result = try preprocess(source: source, options: options) #expect(result == "console.log(\"BAR\");\n") @@ -30,8 +31,8 @@ import Testing @Test func onelineIf() throws { let source = """ - /* #if FOO */console.log("FOO");/* #endif */ - """ + /* #if FOO */console.log("FOO");/* #endif */ + """ let options = PreprocessOptions(conditions: ["FOO": true]) let result = try preprocess(source: source, options: options) #expect(result == "console.log(\"FOO\");") @@ -39,9 +40,9 @@ import Testing @Test func undefinedVariable() throws { let source = """ - /* #if FOO */ - /* #endif */ - """ + /* #if FOO */ + /* #endif */ + """ let options = PreprocessOptions(conditions: [:]) #expect(throws: Error.self) { try preprocess(source: source, options: options) @@ -57,8 +58,8 @@ import Testing @Test func missingEndOfDirective() throws { let source = """ - /* #if FOO - """ + /* #if FOO + """ #expect(throws: Error.self) { try preprocess(source: source, options: PreprocessOptions()) } @@ -72,17 +73,17 @@ import Testing ]) func nestedIfDirectives(foo: Bool, bar: Bool, expected: String) throws { let source = """ - /* #if FOO */ - console.log("FOO"); - /* #if BAR */ - console.log("FOO & BAR"); - /* #else */ - console.log("FOO & !BAR"); - /* #endif */ - /* #else */ - console.log("!FOO"); - /* #endif */ - """ + /* #if FOO */ + console.log("FOO"); + /* #if BAR */ + console.log("FOO & BAR"); + /* #else */ + console.log("FOO & !BAR"); + /* #endif */ + /* #else */ + console.log("!FOO"); + /* #endif */ + """ let options = PreprocessOptions(conditions: ["FOO": foo, "BAR": bar]) let result = try preprocess(source: source, options: options) #expect(result == expected) @@ -90,26 +91,28 @@ import Testing @Test func multipleSubstitutions() throws { let source = """ - const name = "@NAME@"; - const version = "@VERSION@"; - """ + const name = "@NAME@"; + const version = "@VERSION@"; + """ let options = PreprocessOptions(substitutions: [ "NAME": "MyApp", - "VERSION": "1.0.0" + "VERSION": "1.0.0", ]) let result = try preprocess(source: source, options: options) - #expect(result == """ - const name = "MyApp"; - const version = "1.0.0"; - """) + #expect( + result == """ + const name = "MyApp"; + const version = "1.0.0"; + """ + ) } @Test func invalidVariableName() throws { let source = """ - /* #if invalid-name */ - console.log("error"); - /* #endif */ - """ + /* #if invalid-name */ + console.log("error"); + /* #endif */ + """ #expect(throws: Error.self) { try preprocess(source: source, options: PreprocessOptions()) } @@ -117,10 +120,10 @@ import Testing @Test func emptyBlocks() throws { let source = """ - /* #if FOO */ - /* #else */ - /* #endif */ - """ + /* #if FOO */ + /* #else */ + /* #endif */ + """ let options = PreprocessOptions(conditions: ["FOO": true]) let result = try preprocess(source: source, options: options) #expect(result == "") @@ -128,9 +131,9 @@ import Testing @Test func ignoreNonDirectiveComments() throws { let source = """ - /* Normal comment */ - /** Doc comment */ - """ + /* Normal comment */ + /** Doc comment */ + """ let result = try preprocess(source: source, options: PreprocessOptions()) #expect(result == source) } diff --git a/Plugins/PackageToJS/Tests/SnapshotTesting.swift b/Plugins/PackageToJS/Tests/SnapshotTesting.swift index 4732cfce8..e900954ff 100644 --- a/Plugins/PackageToJS/Tests/SnapshotTesting.swift +++ b/Plugins/PackageToJS/Tests/SnapshotTesting.swift @@ -1,11 +1,13 @@ -import Testing import Foundation +import Testing func assertSnapshot( - filePath: String = #filePath, function: String = #function, + filePath: String = #filePath, + function: String = #function, sourceLocation: SourceLocation = #_sourceLocation, variant: String? = nil, - input: Data, fileExtension: String = "json" + input: Data, + fileExtension: String = "json" ) throws { let testFileName = URL(fileURLWithPath: filePath).deletingPathExtension().lastPathComponent let snapshotDir = URL(fileURLWithPath: filePath) @@ -13,7 +15,8 @@ func assertSnapshot( .appendingPathComponent("__Snapshots__") .appendingPathComponent(testFileName) try FileManager.default.createDirectory(at: snapshotDir, withIntermediateDirectories: true) - let snapshotFileName: String = "\(function[..=6.1) && _runtime(_multithreaded) - return $0.ownerTid + return $0.ownerTid #else - _ = $0 - // On single-threaded runtime, source and destination threads are always the main thread (TID = -1). - return -1 + _ = $0 + // On single-threaded runtime, source and destination threads are always the main thread (TID = -1). + return -1 #endif }, transferring: transferring @@ -226,7 +226,11 @@ extension JSSending { /// - Returns: The received object of type `T`. /// - Throws: `JSSendingError` if the sending operation fails, or `JSException` if a JavaScript error occurs. @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - public func receive(isolation: isolated (any Actor)? = #isolation, file: StaticString = #file, line: UInt = #line) async throws -> T { + public func receive( + isolation: isolated (any Actor)? = #isolation, + file: StaticString = #file, + line: UInt = #line + ) async throws -> T { #if compiler(>=6.1) && _runtime(_multithreaded) let idInDestination = try await withCheckedThrowingContinuation { continuation in let context = _JSSendingContext(continuation: continuation) @@ -281,7 +285,9 @@ extension JSSending { @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public static func receive( _ sendings: repeat JSSending, - isolation: isolated (any Actor)? = #isolation, file: StaticString = #file, line: UInt = #line + isolation: isolated (any Actor)? = #isolation, + file: StaticString = #file, + line: UInt = #line ) async throws -> (repeat each U) where T == (repeat each U) { #if compiler(>=6.1) && _runtime(_multithreaded) var sendingObjects: [JavaScriptObjectRef] = [] @@ -329,11 +335,11 @@ extension JSSending { return try await (repeat (each sendings).receive()) #endif } - #endif // compiler(>=6.1) + #endif // compiler(>=6.1) } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) -fileprivate final class _JSSendingContext: Sendable { +private final class _JSSendingContext: Sendable { let continuation: CheckedContinuation init(continuation: CheckedContinuation) { @@ -361,6 +367,7 @@ public struct JSSendingError: Error, CustomStringConvertible { /// - Parameters: /// - object: The `JSObject` to be received. /// - contextPtr: A pointer to the `_JSSendingContext` instance. +// swift-format-ignore #if compiler(>=6.1) // @_expose and @_extern are only available in Swift 6.1+ @_expose(wasm, "swjs_receive_response") @_cdecl("swjs_receive_response") @@ -380,6 +387,7 @@ func _swjs_receive_response(_ object: JavaScriptObjectRef, _ contextPtr: UnsafeR /// - Parameters: /// - error: The error to be received. /// - contextPtr: A pointer to the `_JSSendingContext` instance. +// swift-format-ignore #if compiler(>=6.1) // @_expose and @_extern are only available in Swift 6.1+ @_expose(wasm, "swjs_receive_error") @_cdecl("swjs_receive_error") diff --git a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift index ce4fb1047..6cd8de171 100644 --- a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift +++ b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift @@ -6,35 +6,34 @@ import _CJavaScriptKit #if compiler(>=5.5) -/** Singleton type responsible for integrating JavaScript event loop as a Swift concurrency executor, conforming to -`SerialExecutor` protocol from the standard library. To utilize it: - -1. Make sure that your target depends on `JavaScriptEventLoop` in your `Packages.swift`: - -```swift -.target( - name: "JavaScriptKitExample", - dependencies: [ - "JavaScriptKit", - .product(name: "JavaScriptEventLoop", package: "JavaScriptKit") - ] -) -``` - -2. Add an explicit import in the code that executes **before* you start using `await` and/or `Task` -APIs (most likely in `main.swift`): - -```swift -import JavaScriptEventLoop -``` - -3. Run this function **before* you start using `await` and/or `Task` APIs (again, most likely in -`main.swift`): - -```swift -JavaScriptEventLoop.installGlobalExecutor() -``` -*/ +/// Singleton type responsible for integrating JavaScript event loop as a Swift concurrency executor, conforming to +/// `SerialExecutor` protocol from the standard library. To utilize it: +/// +/// 1. Make sure that your target depends on `JavaScriptEventLoop` in your `Packages.swift`: +/// +/// ```swift +/// .target( +/// name: "JavaScriptKitExample", +/// dependencies: [ +/// "JavaScriptKit", +/// .product(name: "JavaScriptEventLoop", package: "JavaScriptKit") +/// ] +/// ) +/// ``` +/// +/// 2. Add an explicit import in the code that executes **before* you start using `await` and/or `Task` +/// APIs (most likely in `main.swift`): +/// +/// ```swift +/// import JavaScriptEventLoop +/// ``` +/// +/// 3. Run this function **before* you start using `await` and/or `Task` APIs (again, most likely in +/// `main.swift`): +/// +/// ```swift +/// JavaScriptEventLoop.installGlobalExecutor() +/// ``` @available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable { @@ -93,10 +92,13 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable { } }, setTimeout: { delay, job in - setTimeout(JSOneshotClosure { _ in - job() - return JSValue.undefined - }, delay) + setTimeout( + JSOneshotClosure { _ in + job() + return JSValue.undefined + }, + delay + ) } ) return eventLoop @@ -107,7 +109,7 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable { /// Set JavaScript event loop based executor to be the global executor /// Note that this should be called before any of the jobs are created. /// This installation step will be unnecessary after custom executor are - /// introduced officially. See also [a draft proposal for custom + /// introduced officially. See also [a draft proposal for custom /// executors](https://github.com/rjmccall/swift-evolution/blob/custom-executors/proposals/0000-custom-executors.md#the-default-global-concurrent-executor) public static func installGlobalExecutor() { MainActor.assumeIsolated { @@ -119,51 +121,88 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable { guard !didInstallGlobalExecutor else { return } #if compiler(>=5.9) - typealias swift_task_asyncMainDrainQueue_hook_Fn = @convention(thin) (swift_task_asyncMainDrainQueue_original, swift_task_asyncMainDrainQueue_override) -> Void + typealias swift_task_asyncMainDrainQueue_hook_Fn = @convention(thin) ( + swift_task_asyncMainDrainQueue_original, swift_task_asyncMainDrainQueue_override + ) -> Void let swift_task_asyncMainDrainQueue_hook_impl: swift_task_asyncMainDrainQueue_hook_Fn = { _, _ in swjs_unsafe_event_loop_yield() } - swift_task_asyncMainDrainQueue_hook = unsafeBitCast(swift_task_asyncMainDrainQueue_hook_impl, to: UnsafeMutableRawPointer?.self) + swift_task_asyncMainDrainQueue_hook = unsafeBitCast( + swift_task_asyncMainDrainQueue_hook_impl, + to: UnsafeMutableRawPointer?.self + ) #endif - typealias swift_task_enqueueGlobal_hook_Fn = @convention(thin) (UnownedJob, swift_task_enqueueGlobal_original) -> Void + typealias swift_task_enqueueGlobal_hook_Fn = @convention(thin) (UnownedJob, swift_task_enqueueGlobal_original) + -> Void let swift_task_enqueueGlobal_hook_impl: swift_task_enqueueGlobal_hook_Fn = { job, original in JavaScriptEventLoop.shared.unsafeEnqueue(job) } - swift_task_enqueueGlobal_hook = unsafeBitCast(swift_task_enqueueGlobal_hook_impl, to: UnsafeMutableRawPointer?.self) + swift_task_enqueueGlobal_hook = unsafeBitCast( + swift_task_enqueueGlobal_hook_impl, + to: UnsafeMutableRawPointer?.self + ) - typealias swift_task_enqueueGlobalWithDelay_hook_Fn = @convention(thin) (UInt64, UnownedJob, swift_task_enqueueGlobalWithDelay_original) -> Void - let swift_task_enqueueGlobalWithDelay_hook_impl: swift_task_enqueueGlobalWithDelay_hook_Fn = { delay, job, original in + typealias swift_task_enqueueGlobalWithDelay_hook_Fn = @convention(thin) ( + UInt64, UnownedJob, swift_task_enqueueGlobalWithDelay_original + ) -> Void + let swift_task_enqueueGlobalWithDelay_hook_impl: swift_task_enqueueGlobalWithDelay_hook_Fn = { + delay, + job, + original in JavaScriptEventLoop.shared.enqueue(job, withDelay: delay) } - swift_task_enqueueGlobalWithDelay_hook = unsafeBitCast(swift_task_enqueueGlobalWithDelay_hook_impl, to: UnsafeMutableRawPointer?.self) + swift_task_enqueueGlobalWithDelay_hook = unsafeBitCast( + swift_task_enqueueGlobalWithDelay_hook_impl, + to: UnsafeMutableRawPointer?.self + ) #if compiler(>=5.7) - typealias swift_task_enqueueGlobalWithDeadline_hook_Fn = @convention(thin) (Int64, Int64, Int64, Int64, Int32, UnownedJob, swift_task_enqueueGlobalWithDelay_original) -> Void - let swift_task_enqueueGlobalWithDeadline_hook_impl: swift_task_enqueueGlobalWithDeadline_hook_Fn = { sec, nsec, tsec, tnsec, clock, job, original in + typealias swift_task_enqueueGlobalWithDeadline_hook_Fn = @convention(thin) ( + Int64, Int64, Int64, Int64, Int32, UnownedJob, swift_task_enqueueGlobalWithDelay_original + ) -> Void + let swift_task_enqueueGlobalWithDeadline_hook_impl: swift_task_enqueueGlobalWithDeadline_hook_Fn = { + sec, + nsec, + tsec, + tnsec, + clock, + job, + original in JavaScriptEventLoop.shared.enqueue(job, withDelay: sec, nsec, tsec, tnsec, clock) } - swift_task_enqueueGlobalWithDeadline_hook = unsafeBitCast(swift_task_enqueueGlobalWithDeadline_hook_impl, to: UnsafeMutableRawPointer?.self) + swift_task_enqueueGlobalWithDeadline_hook = unsafeBitCast( + swift_task_enqueueGlobalWithDeadline_hook_impl, + to: UnsafeMutableRawPointer?.self + ) #endif - typealias swift_task_enqueueMainExecutor_hook_Fn = @convention(thin) (UnownedJob, swift_task_enqueueMainExecutor_original) -> Void + typealias swift_task_enqueueMainExecutor_hook_Fn = @convention(thin) ( + UnownedJob, swift_task_enqueueMainExecutor_original + ) -> Void let swift_task_enqueueMainExecutor_hook_impl: swift_task_enqueueMainExecutor_hook_Fn = { job, original in JavaScriptEventLoop.shared.unsafeEnqueue(job) } - swift_task_enqueueMainExecutor_hook = unsafeBitCast(swift_task_enqueueMainExecutor_hook_impl, to: UnsafeMutableRawPointer?.self) + swift_task_enqueueMainExecutor_hook = unsafeBitCast( + swift_task_enqueueMainExecutor_hook_impl, + to: UnsafeMutableRawPointer?.self + ) didInstallGlobalExecutor = true } private func enqueue(_ job: UnownedJob, withDelay nanoseconds: UInt64) { let milliseconds = nanoseconds / 1_000_000 - setTimeout(Double(milliseconds), { - #if compiler(>=5.9) - job.runSynchronously(on: self.asUnownedSerialExecutor()) - #else - job._runSynchronously(on: self.asUnownedSerialExecutor()) - #endif - }) + setTimeout( + Double(milliseconds), + { + #if compiler(>=5.9) + job.runSynchronously(on: self.asUnownedSerialExecutor()) + #else + job._runSynchronously(on: self.asUnownedSerialExecutor()) + #endif + } + ) } private func unsafeEnqueue(_ job: UnownedJob) { @@ -192,15 +231,19 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable { /// Taken from https://github.com/apple/swift/blob/d375c972f12128ec6055ed5f5337bfcae3ec67d8/stdlib/public/Concurrency/Clock.swift#L84-L88 @_silgen_name("swift_get_time") internal func swift_get_time( - _ seconds: UnsafeMutablePointer, - _ nanoseconds: UnsafeMutablePointer, - _ clock: CInt) + _ seconds: UnsafeMutablePointer, + _ nanoseconds: UnsafeMutablePointer, + _ clock: CInt +) @available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) extension JavaScriptEventLoop { fileprivate func enqueue( - _ job: UnownedJob, withDelay seconds: Int64, _ nanoseconds: Int64, - _ toleranceSec: Int64, _ toleranceNSec: Int64, + _ job: UnownedJob, + withDelay seconds: Int64, + _ nanoseconds: Int64, + _ toleranceSec: Int64, + _ toleranceNSec: Int64, _ clock: Int32 ) { var nowSec: Int64 = 0 @@ -213,9 +256,9 @@ extension JavaScriptEventLoop { #endif @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) -public extension JSPromise { +extension JSPromise { /// Wait for the promise to complete, returning (or throwing) its result. - var value: JSValue { + public var value: JSValue { get async throws { try await withUnsafeThrowingContinuation { [self] continuation in self.then( @@ -235,7 +278,7 @@ public extension JSPromise { /// Wait for the promise to complete, returning its result or exception as a Result. /// /// - Note: Calling this function does not switch from the caller's isolation domain. - func value(isolation: isolated (any Actor)? = #isolation) async throws -> JSValue { + public func value(isolation: isolated (any Actor)? = #isolation) async throws -> JSValue { try await withUnsafeThrowingContinuation(isolation: isolation) { [self] continuation in self.then( success: { @@ -251,7 +294,7 @@ public extension JSPromise { } /// Wait for the promise to complete, returning its result or exception as a Result. - var result: JSPromise.Result { + public var result: JSPromise.Result { get async { await withUnsafeContinuation { [self] continuation in self.then( diff --git a/Sources/JavaScriptEventLoop/JobQueue.swift b/Sources/JavaScriptEventLoop/JobQueue.swift index 5ad71f0a0..cb583dae3 100644 --- a/Sources/JavaScriptEventLoop/JobQueue.swift +++ b/Sources/JavaScriptEventLoop/JobQueue.swift @@ -63,18 +63,18 @@ extension JavaScriptEventLoop { } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) -fileprivate extension UnownedJob { +extension UnownedJob { private func asImpl() -> UnsafeMutablePointer<_CJavaScriptEventLoop.Job> { unsafeBitCast(self, to: UnsafeMutablePointer<_CJavaScriptEventLoop.Job>.self) } - var flags: JobFlags { + fileprivate var flags: JobFlags { JobFlags(bits: asImpl().pointee.Flags) } - var rawPriority: UInt32 { flags.priority } + fileprivate var rawPriority: UInt32 { flags.priority } - func nextInQueue() -> UnsafeMutablePointer { + fileprivate func nextInQueue() -> UnsafeMutablePointer { return withUnsafeMutablePointer(to: &asImpl().pointee.SchedulerPrivate.0) { rawNextJobPtr in let nextJobPtr = UnsafeMutableRawPointer(rawNextJobPtr).bindMemory(to: UnownedJob?.self, capacity: 1) return nextJobPtr @@ -83,13 +83,11 @@ fileprivate extension UnownedJob { } -fileprivate struct JobFlags { - var bits: UInt32 = 0 +private struct JobFlags { + var bits: UInt32 = 0 - var priority: UInt32 { - get { - (bits & 0xFF00) >> 8 + var priority: UInt32 { + (bits & 0xFF00) >> 8 } - } } #endif diff --git a/Sources/JavaScriptEventLoop/WebWorkerDedicatedExecutor.swift b/Sources/JavaScriptEventLoop/WebWorkerDedicatedExecutor.swift index 695eb9c61..eecaf93c5 100644 --- a/Sources/JavaScriptEventLoop/WebWorkerDedicatedExecutor.swift +++ b/Sources/JavaScriptEventLoop/WebWorkerDedicatedExecutor.swift @@ -2,11 +2,11 @@ import JavaScriptKit import _CJavaScriptEventLoop #if canImport(Synchronization) - import Synchronization +import Synchronization #endif #if canImport(wasi_pthread) - import wasi_pthread - import WASILibc +import wasi_pthread +import WASILibc #endif /// A serial executor that runs on a dedicated web worker thread. @@ -42,7 +42,9 @@ public final class WebWorkerDedicatedExecutor: SerialExecutor { /// - Throws: An error if any worker thread fails to initialize within the timeout period. public init(timeout: Duration = .seconds(3), checkInterval: Duration = .microseconds(5)) async throws { let underlying = try await WebWorkerTaskExecutor( - numberOfThreads: 1, timeout: timeout, checkInterval: checkInterval + numberOfThreads: 1, + timeout: timeout, + checkInterval: checkInterval ) self.underlying = underlying } diff --git a/Sources/JavaScriptEventLoop/WebWorkerTaskExecutor.swift b/Sources/JavaScriptEventLoop/WebWorkerTaskExecutor.swift index 7373b9604..f47cb1b9c 100644 --- a/Sources/JavaScriptEventLoop/WebWorkerTaskExecutor.swift +++ b/Sources/JavaScriptEventLoop/WebWorkerTaskExecutor.swift @@ -1,15 +1,15 @@ -#if compiler(>=6.0) // `TaskExecutor` is available since Swift 6.0 +#if compiler(>=6.0) // `TaskExecutor` is available since Swift 6.0 import JavaScriptKit import _CJavaScriptKit import _CJavaScriptEventLoop #if canImport(Synchronization) - import Synchronization +import Synchronization #endif #if canImport(wasi_pthread) - import wasi_pthread - import WASILibc +import wasi_pthread +import WASILibc #endif // MARK: - Web Worker Task Executor @@ -23,13 +23,13 @@ import _CJavaScriptEventLoop /// /// ## Multithreading Model /// -/// Each task submitted to the executor runs on one of the available worker threads. By default, +/// Each task submitted to the executor runs on one of the available worker threads. By default, /// child tasks created within a worker thread continue to run on the same worker thread, /// maintaining thread locality and avoiding excessive context switching. /// /// ## Object Sharing Between Threads /// -/// When working with JavaScript objects across threads, you must use the `JSSending` API to +/// When working with JavaScript objects across threads, you must use the `JSSending` API to /// explicitly transfer or clone objects: /// /// ```swift @@ -80,7 +80,7 @@ import _CJavaScriptEventLoop /// return fibonacci(i) /// } /// } -/// +/// /// for await result in group { /// // Process results as they complete /// } @@ -106,8 +106,8 @@ import _CJavaScriptEventLoop /// // Back to the main thread. /// } /// ```` -/// -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) // For `Atomic` and `TaskExecutor` types +/// +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) // For `Atomic` and `TaskExecutor` types public final class WebWorkerTaskExecutor: TaskExecutor { /// A job worker dedicated to a single Web Worker thread. @@ -199,10 +199,12 @@ public final class WebWorkerTaskExecutor: TaskExecutor { // like `setTimeout` or `addEventListener`. // We can run the job and subsequently spawned jobs immediately. // JSPromise.resolve(JSValue.undefined).then { _ in - _ = JSObject.global.queueMicrotask!(JSOneshotClosure { _ in - self.run() - return JSValue.undefined - }) + _ = JSObject.global.queueMicrotask!( + JSOneshotClosure { _ in + self.run() + return JSValue.undefined + } + ) } else { let tid = self.tid.load(ordering: .sequentiallyConsistent) swjs_wake_up_worker_thread(tid) @@ -220,10 +222,12 @@ public final class WebWorkerTaskExecutor: TaskExecutor { } func scheduleNextRun() { - _ = JSObject.global.queueMicrotask!(JSOneshotClosure { _ in - self.run() - return JSValue.undefined - }) + _ = JSObject.global.queueMicrotask!( + JSOneshotClosure { _ in + self.run() + return JSValue.undefined + } + ) } /// Run the worker @@ -277,18 +281,22 @@ public final class WebWorkerTaskExecutor: TaskExecutor { return job } // No more jobs to run now. Wait for a new job to be enqueued. - let (exchanged, original) = state.compareExchange(expected: .running, desired: .idle, ordering: .sequentiallyConsistent) + let (exchanged, original) = state.compareExchange( + expected: .running, + desired: .idle, + ordering: .sequentiallyConsistent + ) switch (exchanged, original) { case (true, _): trace("Worker.run exited \(original) -> idle") - return nil // Regular case + return nil // Regular case case (false, .idle): preconditionFailure("unreachable: Worker/run running in multiple threads!?") case (false, .running): preconditionFailure("unreachable: running -> idle should return exchanged=true") case (false, .terminated): - return nil // The worker is terminated, exit the loop. + return nil // The worker is terminated, exit the loop. } } guard let job else { return } @@ -347,16 +355,21 @@ public final class WebWorkerTaskExecutor: TaskExecutor { // immediately. The context must be retained until the thread is started. let context = Context(executor: self, worker: worker) let ptr = Unmanaged.passRetained(context).toOpaque() - let ret = pthread_create(nil, nil, { ptr in - // Cast to a optional pointer to absorb nullability variations between platforms. - let ptr: UnsafeMutableRawPointer? = ptr - let context = Unmanaged.fromOpaque(ptr!).takeRetainedValue() - context.worker.start(executor: context.executor) - // The worker is started. Throw JS exception to unwind the call stack without - // reaching the `pthread_exit`, which is called immediately after this block. - swjs_unsafe_event_loop_yield() - return nil - }, ptr) + let ret = pthread_create( + nil, + nil, + { ptr in + // Cast to a optional pointer to absorb nullability variations between platforms. + let ptr: UnsafeMutableRawPointer? = ptr + let context = Unmanaged.fromOpaque(ptr!).takeRetainedValue() + context.worker.start(executor: context.executor) + // The worker is started. Throw JS exception to unwind the call stack without + // reaching the `pthread_exit`, which is called immediately after this block. + swjs_unsafe_event_loop_yield() + return nil + }, + ptr + ) precondition(ret == 0, "Failed to create a thread") } // Wait until all worker threads are started and wire up messaging channels @@ -432,15 +445,19 @@ public final class WebWorkerTaskExecutor: TaskExecutor { /// - timeout: The maximum time to wait for all worker threads to be started. Default is 3 seconds. /// - checkInterval: The interval to check if all worker threads are started. Default is 5 microseconds. /// - Throws: An error if any worker thread fails to initialize within the timeout period. - public init(numberOfThreads: Int, timeout: Duration = .seconds(3), checkInterval: Duration = .microseconds(5)) async throws { + public init( + numberOfThreads: Int, + timeout: Duration = .seconds(3), + checkInterval: Duration = .microseconds(5) + ) async throws { self.executor = Executor(numberOfThreads: numberOfThreads) try await self.executor.start(timeout: timeout, checkInterval: checkInterval) } /// Terminates all worker threads managed by this executor. /// - /// This method should be called when the executor is no longer needed to free up - /// resources. After calling this method, any tasks enqueued to this executor will + /// This method should be called when the executor is no longer needed to free up + /// resources. After calling this method, any tasks enqueued to this executor will /// be ignored and may never complete. /// /// It's recommended to use a `defer` statement immediately after creating the executor @@ -533,7 +550,7 @@ public final class WebWorkerTaskExecutor: TaskExecutor { /// Installs a global executor that forwards jobs from Web Worker threads to the main thread. /// /// This method sets up the necessary hooks to ensure proper task scheduling between - /// the main thread and worker threads. It must be called once (typically at application + /// the main thread and worker threads. It must be called once (typically at application /// startup) before using any `WebWorkerTaskExecutor` instances. /// /// ## Example @@ -564,14 +581,18 @@ public final class WebWorkerTaskExecutor: TaskExecutor { _swift_task_enqueueGlobal_hook_original = swift_task_enqueueGlobal_hook - typealias swift_task_enqueueGlobal_hook_Fn = @convention(thin) (UnownedJob, swift_task_enqueueGlobal_original) -> Void + typealias swift_task_enqueueGlobal_hook_Fn = @convention(thin) (UnownedJob, swift_task_enqueueGlobal_original) + -> Void let swift_task_enqueueGlobal_hook_impl: swift_task_enqueueGlobal_hook_Fn = { job, base in WebWorkerTaskExecutor.traceStatsIncrement(\.enqueueGlobal) // Enter this block only if the current Task has no executor preference. if pthread_equal(pthread_self(), WebWorkerTaskExecutor._mainThread) != 0 { // If the current thread is the main thread, delegate the job // execution to the original hook of JavaScriptEventLoop. - let original = unsafeBitCast(WebWorkerTaskExecutor._swift_task_enqueueGlobal_hook_original, to: swift_task_enqueueGlobal_hook_Fn.self) + let original = unsafeBitCast( + WebWorkerTaskExecutor._swift_task_enqueueGlobal_hook_original, + to: swift_task_enqueueGlobal_hook_Fn.self + ) original(job, base) } else { // Notify the main thread to execute the job when a job is @@ -583,7 +604,10 @@ public final class WebWorkerTaskExecutor: TaskExecutor { swjs_send_job_to_main_thread(jobBitPattern) } } - swift_task_enqueueGlobal_hook = unsafeBitCast(swift_task_enqueueGlobal_hook_impl, to: UnsafeMutableRawPointer?.self) + swift_task_enqueueGlobal_hook = unsafeBitCast( + swift_task_enqueueGlobal_hook_impl, + to: UnsafeMutableRawPointer?.self + ) #else fatalError("Unsupported platform") #endif @@ -593,7 +617,7 @@ public final class WebWorkerTaskExecutor: TaskExecutor { /// Enqueue a job scheduled from a Web Worker thread to the main thread. /// This function is called when a job is enqueued from a Web Worker thread. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -#if compiler(>=6.1) // @_expose and @_extern are only available in Swift 6.1+ +#if compiler(>=6.1) // @_expose and @_extern are only available in Swift 6.1+ @_expose(wasm, "swjs_enqueue_main_job_from_worker") #endif func _swjs_enqueue_main_job_from_worker(_ job: UnownedJob) { @@ -604,17 +628,17 @@ func _swjs_enqueue_main_job_from_worker(_ job: UnownedJob) { /// Wake up the worker thread. /// This function is called when a job is enqueued from the main thread to a worker thread. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -#if compiler(>=6.1) // @_expose and @_extern are only available in Swift 6.1+ +#if compiler(>=6.1) // @_expose and @_extern are only available in Swift 6.1+ @_expose(wasm, "swjs_wake_worker_thread") #endif func _swjs_wake_worker_thread() { WebWorkerTaskExecutor.Worker.currentThread!.run() } -fileprivate func trace(_ message: String) { -#if JAVASCRIPTKIT_TRACE +private func trace(_ message: String) { + #if JAVASCRIPTKIT_TRACE JSObject.global.process.stdout.write("[trace tid=\(swjs_get_worker_thread_id())] \(message)\n") -#endif + #endif } -#endif // compiler(>=6.0) +#endif // compiler(>=6.0) diff --git a/Sources/JavaScriptKit/BasicObjects/JSArray.swift b/Sources/JavaScriptKit/BasicObjects/JSArray.swift index 56345d085..fad602465 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSArray.swift @@ -98,8 +98,8 @@ private func getObjectValuesLength(_ object: JSObject) -> Int { return Int(values.length.number!) } -public extension JSValue { - var array: JSArray? { +extension JSValue { + public var array: JSArray? { object.flatMap(JSArray.init) } } diff --git a/Sources/JavaScriptKit/BasicObjects/JSDate.swift b/Sources/JavaScriptKit/BasicObjects/JSDate.swift index c8a6623a1..9157796bc 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSDate.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSDate.swift @@ -1,11 +1,10 @@ -/** A wrapper around the [JavaScript `Date` - class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) that - exposes its properties in a type-safe way. This doesn't 100% match the JS API, for example - `getMonth`/`setMonth` etc accessor methods are converted to properties, but the rest of it matches - in the naming. Parts of the JavaScript `Date` API that are not consistent across browsers and JS - implementations are not exposed in a type-safe manner, you should access the underlying `jsObject` - property if you need those. - */ +/// A wrapper around the [JavaScript `Date` +/// class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) that +/// exposes its properties in a type-safe way. This doesn't 100% match the JS API, for example +/// `getMonth`/`setMonth` etc accessor methods are converted to properties, but the rest of it matches +/// in the naming. Parts of the JavaScript `Date` API that are not consistent across browsers and JS +/// implementations are not exposed in a type-safe manner, you should access the underlying `jsObject` +/// property if you need those. public final class JSDate: JSBridgedClass { /// The constructor function used to create new `Date` objects. public static var constructor: JSFunction? { _constructor.wrappedValue } diff --git a/Sources/JavaScriptKit/BasicObjects/JSError.swift b/Sources/JavaScriptKit/BasicObjects/JSError.swift index 0f87d3c67..38accb97b 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSError.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSError.swift @@ -1,7 +1,6 @@ -/** A wrapper around [the JavaScript `Error` - class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) that - exposes its properties in a type-safe way. - */ +/// A wrapper around [the JavaScript `Error` +/// class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) that +/// exposes its properties in a type-safe way. public final class JSError: JSBridgedClass { /// The constructor function used to create new JavaScript `Error` objects. public static var constructor: JSFunction? { _constructor.wrappedValue } diff --git a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift index cfe32d515..7502bb5f1 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift @@ -27,7 +27,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 let .object(jsObject) = value else { return nil } + guard case .object(let jsObject) = value else { return nil } return Self(jsObject) } @@ -55,9 +55,9 @@ public final class JSPromise: JSBridgedClass { resolver { switch $0 { - case let .success(success): + case .success(let success): resolve(success) - case let .failure(error): + case .failure(let error): reject(error) } } @@ -66,7 +66,7 @@ public final class JSPromise: JSBridgedClass { self.init(unsafelyWrapping: Self.constructor!.new(closure)) } -#if !hasFeature(Embedded) + #if !hasFeature(Embedded) public static func resolve(_ value: ConvertibleToJSValue) -> JSPromise { self.init(unsafelyWrapping: Self.constructor!.resolve!(value).object!) } @@ -74,7 +74,7 @@ public final class JSPromise: JSBridgedClass { public static func reject(_ reason: ConvertibleToJSValue) -> JSPromise { self.init(unsafelyWrapping: Self.constructor!.reject!(reason).object!) } -#else + #else public static func resolve(_ value: some ConvertibleToJSValue) -> JSPromise { self.init(unsafelyWrapping: constructor!.resolve!(value).object!) } @@ -82,9 +82,9 @@ public final class JSPromise: JSBridgedClass { public static func reject(_ reason: some ConvertibleToJSValue) -> JSPromise { self.init(unsafelyWrapping: constructor!.reject!(reason).object!) } -#endif + #endif -#if !hasFeature(Embedded) + #if !hasFeature(Embedded) /// Schedules the `success` closure to be invoked on successful completion of `self`. @discardableResult public func then(success: @escaping (JSValue) -> ConvertibleToJSValue) -> JSPromise { @@ -95,15 +95,15 @@ public final class JSPromise: JSBridgedClass { } #if compiler(>=5.5) - /// Schedules the `success` closure to be invoked on successful completion of `self`. - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - @discardableResult - public func then(success: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue) -> JSPromise { - let closure = JSOneshotClosure.async { - try await success($0[0]).jsValue - } - return JSPromise(unsafelyWrapping: jsObject.then!(closure).object!) + /// Schedules the `success` closure to be invoked on successful completion of `self`. + @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public func then(success: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue) -> JSPromise { + let closure = JSOneshotClosure.async { + try await success($0[0]).jsValue } + return JSPromise(unsafelyWrapping: jsObject.then!(closure).object!) + } #endif /// Schedules the `success` closure to be invoked on successful completion of `self`. @@ -122,20 +122,21 @@ public final class JSPromise: JSBridgedClass { } #if compiler(>=5.5) - /// Schedules the `success` closure to be invoked on successful completion of `self`. - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - @discardableResult - public func then(success: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue, - failure: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue) -> JSPromise - { - let successClosure = JSOneshotClosure.async { - try await success($0[0]).jsValue - } - let failureClosure = JSOneshotClosure.async { - try await failure($0[0]).jsValue - } - return JSPromise(unsafelyWrapping: jsObject.then!(successClosure, failureClosure).object!) + /// Schedules the `success` closure to be invoked on successful completion of `self`. + @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public func then( + success: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue, + failure: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue + ) -> JSPromise { + let successClosure = JSOneshotClosure.async { + try await success($0[0]).jsValue + } + let failureClosure = JSOneshotClosure.async { + try await failure($0[0]).jsValue } + return JSPromise(unsafelyWrapping: jsObject.then!(successClosure, failureClosure).object!) + } #endif /// Schedules the `failure` closure to be invoked on rejected completion of `self`. @@ -148,15 +149,16 @@ public final class JSPromise: JSBridgedClass { } #if compiler(>=5.5) - /// Schedules the `failure` closure to be invoked on rejected completion of `self`. - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - @discardableResult - public func `catch`(failure: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue) -> JSPromise { - let closure = JSOneshotClosure.async { - try await failure($0[0]).jsValue - } - return .init(unsafelyWrapping: jsObject.catch!(closure).object!) + /// Schedules the `failure` closure to be invoked on rejected completion of `self`. + @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public func `catch`(failure: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue) -> JSPromise + { + let closure = JSOneshotClosure.async { + try await failure($0[0]).jsValue } + return .init(unsafelyWrapping: jsObject.catch!(closure).object!) + } #endif /// Schedules the `failure` closure to be invoked on either successful or rejected @@ -169,5 +171,5 @@ public final class JSPromise: JSBridgedClass { } return .init(unsafelyWrapping: jsObject.finally!(closure).object!) } -#endif + #endif } diff --git a/Sources/JavaScriptKit/BasicObjects/JSTimer.swift b/Sources/JavaScriptKit/BasicObjects/JSTimer.swift index 231792a84..3655a185c 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTimer.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTimer.swift @@ -1,15 +1,14 @@ -/** This timer is an abstraction over [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval) -/ [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearInterval) and -[`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout) -/ [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout) -JavaScript functions. It intentionally doesn't match the JavaScript API, as a special care is -needed to hold a reference to the timer closure and to call `JSClosure.release()` on it when the -timer is deallocated. As a user, you have to hold a reference to a `JSTimer` instance for it to stay -valid. The `JSTimer` API is also intentionally trivial, the timer is started right away, and the -only way to invalidate the timer is to bring the reference count of the `JSTimer` instance to zero. -For invalidation you should either store the timer in an optional property and assign `nil` to it, -or deallocate the object that owns the timer. -*/ +/// This timer is an abstraction over [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval) +/// / [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearInterval) and +/// [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout) +/// / [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout) +/// JavaScript functions. It intentionally doesn't match the JavaScript API, as a special care is +/// needed to hold a reference to the timer closure and to call `JSClosure.release()` on it when the +/// timer is deallocated. As a user, you have to hold a reference to a `JSTimer` instance for it to stay +/// valid. The `JSTimer` API is also intentionally trivial, the timer is started right away, and the +/// only way to invalidate the timer is to bring the reference count of the `JSTimer` instance to zero. +/// For invalidation you should either store the timer in an optional property and assign `nil` to it, +/// or deallocate the object that owns the timer. public final class JSTimer { enum ClosureStorage { case oneshot(JSOneshotClosure) @@ -27,11 +26,11 @@ public final class JSTimer { case .oneshot(let closure): closure.release() case .repeating(let closure): -#if JAVASCRIPTKIT_WITHOUT_WEAKREFS + #if JAVASCRIPTKIT_WITHOUT_WEAKREFS closure.release() -#else + #else break // no-op -#endif + #endif } } } @@ -58,17 +57,21 @@ public final class JSTimer { `millisecondsDelay` intervals indefinitely until the timer is deallocated. - callback: the closure to be executed after a given `millisecondsDelay` interval. */ - public init(millisecondsDelay: Double, isRepeating: Bool = false, callback: @escaping () -> ()) { + public init(millisecondsDelay: Double, isRepeating: Bool = false, callback: @escaping () -> Void) { if isRepeating { - closure = .repeating(JSClosure { _ in - callback() - return .undefined - }) + closure = .repeating( + JSClosure { _ in + callback() + return .undefined + } + ) } else { - closure = .oneshot(JSOneshotClosure { _ in - callback() - return .undefined - }) + closure = .oneshot( + JSOneshotClosure { _ in + callback() + return .undefined + } + ) } self.isRepeating = isRepeating if isRepeating { diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index dec834bbd..19602a314 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -97,35 +97,35 @@ public class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiteral wh } #if compiler(>=5.5) - /// Calls the given async closure with a pointer to a copy of the underlying bytes of the - /// array's storage. - /// - /// - Note: The pointer passed as an argument to `body` is valid only for the - /// lifetime of the closure. Do not escape it from the async closure for later - /// use. - /// - /// - Parameter body: A closure with an `UnsafeBufferPointer` parameter - /// that points to the contiguous storage for the array. - /// If `body` has a return value, that value is also - /// used as the return value for the `withUnsafeBytes(_:)` method. The - /// argument is valid only for the duration of the closure's execution. - /// - Returns: The return value, if any, of the `body`async closure parameter. - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - public func withUnsafeBytesAsync(_ body: (UnsafeBufferPointer) async throws -> R) async rethrows -> R { - let bytesLength = lengthInBytes - let rawBuffer = UnsafeMutableBufferPointer.allocate(capacity: bytesLength) - defer { rawBuffer.deallocate() } - let baseAddress = rawBuffer.baseAddress! - swjs_load_typed_array(jsObject.id, baseAddress) - let length = bytesLength / MemoryLayout.size - let rawBaseAddress = UnsafeRawPointer(baseAddress) - let bufferPtr = UnsafeBufferPointer( - start: rawBaseAddress.assumingMemoryBound(to: Element.self), - count: length - ) - let result = try await body(bufferPtr) - return result - } + /// Calls the given async closure with a pointer to a copy of the underlying bytes of the + /// array's storage. + /// + /// - Note: The pointer passed as an argument to `body` is valid only for the + /// lifetime of the closure. Do not escape it from the async closure for later + /// use. + /// + /// - Parameter body: A closure with an `UnsafeBufferPointer` parameter + /// that points to the contiguous storage for the array. + /// If `body` has a return value, that value is also + /// used as the return value for the `withUnsafeBytes(_:)` method. The + /// argument is valid only for the duration of the closure's execution. + /// - Returns: The return value, if any, of the `body`async closure parameter. + @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) + public func withUnsafeBytesAsync(_ body: (UnsafeBufferPointer) async throws -> R) async rethrows -> R { + let bytesLength = lengthInBytes + let rawBuffer = UnsafeMutableBufferPointer.allocate(capacity: bytesLength) + defer { rawBuffer.deallocate() } + let baseAddress = rawBuffer.baseAddress! + swjs_load_typed_array(jsObject.id, baseAddress) + let length = bytesLength / MemoryLayout.size + let rawBaseAddress = UnsafeRawPointer(baseAddress) + let bufferPtr = UnsafeBufferPointer( + start: rawBaseAddress.assumingMemoryBound(to: Element.self), + count: length + ) + let result = try await body(bufferPtr) + return result + } #endif } @@ -138,7 +138,9 @@ func valueForBitWidth(typeName: String, bitWidth: Int, when32: T) -> T { } else if bitWidth == 64 { fatalError("64-bit \(typeName)s are not yet supported in JSTypedArray") } else { - fatalError("Unsupported bit width for type \(typeName): \(bitWidth) (hint: stick to fixed-size \(typeName)s to avoid this issue)") + fatalError( + "Unsupported bit width for type \(typeName): \(bitWidth) (hint: stick to fixed-size \(typeName)s to avoid this issue)" + ) } } diff --git a/Sources/JavaScriptKit/ConstructibleFromJSValue.swift b/Sources/JavaScriptKit/ConstructibleFromJSValue.swift index f0e0ad431..d4a5921cd 100644 --- a/Sources/JavaScriptKit/ConstructibleFromJSValue.swift +++ b/Sources/JavaScriptKit/ConstructibleFromJSValue.swift @@ -68,11 +68,11 @@ extension SignedInteger where Self: ConstructibleFromJSValue { if let number = value.number { return Self(exactly: number.rounded(.towardZero)) } -#if !hasFeature(Embedded) + #if !hasFeature(Embedded) if let bigInt = value.bigInt as? JSBigIntExtended { return Self(exactly: bigInt) } -#endif + #endif return nil } } @@ -116,11 +116,11 @@ extension UnsignedInteger where Self: ConstructibleFromJSValue { if let number = value.number { return Self(exactly: number.rounded(.towardZero)) } -#if !hasFeature(Embedded) + #if !hasFeature(Embedded) if let bigInt = value.bigInt as? JSBigIntExtended { return Self(exactly: bigInt) } -#endif + #endif return nil } } diff --git a/Sources/JavaScriptKit/ConvertibleToJSValue.swift b/Sources/JavaScriptKit/ConvertibleToJSValue.swift index a7f7da8b6..805ee74d5 100644 --- a/Sources/JavaScriptKit/ConvertibleToJSValue.swift +++ b/Sources/JavaScriptKit/ConvertibleToJSValue.swift @@ -63,7 +63,6 @@ extension UInt32: ConvertibleToJSValue { public var jsValue: JSValue { .number(Double(self)) } } - extension Int8: ConvertibleToJSValue { public var jsValue: JSValue { .number(Double(self)) } } @@ -147,7 +146,7 @@ extension Optional: ConvertibleToJSValue where Wrapped: ConvertibleToJSValue { public var jsValue: JSValue { switch self { case .none: return .null - case let .some(wrapped): return wrapped.jsValue + case .some(let wrapped): return wrapped.jsValue } } } @@ -185,7 +184,7 @@ extension Array: ConstructibleFromJSValue where Element: ConstructibleFromJSValu var array = [Element]() array.reserveCapacity(count) - for i in 0 ..< count { + for i in 0.. T + _ values: Self, + _ index: Int, + _ results: inout [RawJSValue], + _ body: ([RawJSValue]) -> T ) -> T { if index == values.count { return body(results) } return values[index].jsValue.withRawJSValue { (rawValue) -> T in @@ -285,8 +286,10 @@ extension Array where Element == ConvertibleToJSValue { guard self.count != 0 else { return body([]) } func _withRawJSValues( - _ values: [ConvertibleToJSValue], _ index: Int, - _ results: inout [RawJSValue], _ body: ([RawJSValue]) -> T + _ values: [ConvertibleToJSValue], + _ index: Int, + _ results: inout [RawJSValue], + _ body: ([RawJSValue]) -> T ) -> T { if index == values.count { return body(results) } return values[index].jsValue.withRawJSValue { (rawValue) -> T in diff --git a/Sources/JavaScriptKit/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-2-1-main-swift.swift b/Sources/JavaScriptKit/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-2-1-main-swift.swift index 156ac0540..a528e65b1 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-2-1-main-swift.swift +++ b/Sources/JavaScriptKit/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-2-1-main-swift.swift @@ -3,4 +3,4 @@ import JavaScriptKit let document = JSObject.global.document var div = document.createElement("div") div.innerText = "Hello from Swift!" -document.body.appendChild(div) +document.body.appendChild(div) diff --git a/Sources/JavaScriptKit/Features.swift b/Sources/JavaScriptKit/Features.swift index db6e00f26..313edab40 100644 --- a/Sources/JavaScriptKit/Features.swift +++ b/Sources/JavaScriptKit/Features.swift @@ -5,9 +5,9 @@ enum LibraryFeatures { @_cdecl("_library_features") func _library_features() -> Int32 { var features: Int32 = 0 -#if !JAVASCRIPTKIT_WITHOUT_WEAKREFS + #if !JAVASCRIPTKIT_WITHOUT_WEAKREFS features |= LibraryFeatures.weakRefs -#endif + #endif return features } @@ -15,4 +15,4 @@ func _library_features() -> Int32 { // cdecls currently don't work in embedded, and expose for wasm only works >=6.0 @_expose(wasm, "swjs_library_features") public func _swjs_library_features() -> Int32 { _library_features() } -#endif \ No newline at end of file +#endif diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSBigInt.swift b/Sources/JavaScriptKit/FundamentalObjects/JSBigInt.swift index a8867f95c..3f0c2632b 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSBigInt.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSBigInt.swift @@ -10,18 +10,18 @@ public final class JSBigInt: JSObject { override public init(id: JavaScriptObjectRef) { super.init(id: id) } - + /// Instantiate a new `JSBigInt` with given Int64 value in a slow path /// This doesn't require [JS-BigInt-integration](https://github.com/WebAssembly/JS-BigInt-integration) feature. public init(_slowBridge value: Int64) { let value = UInt64(bitPattern: value) - super.init(id: swjs_i64_to_bigint_slow(UInt32(value & 0xffffffff), UInt32(value >> 32), true)) + super.init(id: swjs_i64_to_bigint_slow(UInt32(value & 0xffff_ffff), UInt32(value >> 32), true)) } /// Instantiate a new `JSBigInt` with given UInt64 value in a slow path /// This doesn't require [JS-BigInt-integration](https://github.com/WebAssembly/JS-BigInt-integration) feature. public init(_slowBridge value: UInt64) { - super.init(id: swjs_i64_to_bigint_slow(UInt32(value & 0xffffffff), UInt32(value >> 32), false)) + super.init(id: swjs_i64_to_bigint_slow(UInt32(value & 0xffff_ffff), UInt32(value >> 32), false)) } override public var jsValue: JSValue { diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift index 828cb40f2..66ce009bf 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift @@ -26,15 +26,19 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol { } // 3. Retain the given body in static storage by `funcRef`. - JSClosure.sharedClosures.wrappedValue[hostFuncRef] = (self, { - defer { self.release() } - return body($0) - }) + JSClosure.sharedClosures.wrappedValue[hostFuncRef] = ( + self, + { + defer { self.release() } + return body($0) + } + ) } #if compiler(>=5.5) && !hasFeature(Embedded) @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - public static func async(_ body: sending @escaping (sending [JSValue]) async throws -> JSValue) -> JSOneshotClosure { + public static func async(_ body: sending @escaping (sending [JSValue]) async throws -> JSValue) -> JSOneshotClosure + { JSOneshotClosure(makeAsyncClosure(body)) } #endif @@ -90,9 +94,14 @@ public class JSClosure: JSFunction, JSClosureProtocol { private var isReleased: Bool = false #endif - @available(*, deprecated, message: "This initializer will be removed in the next minor version update. Please use `init(_ body: @escaping ([JSValue]) -> JSValue)` and add `return .undefined` to the end of your closure") + @available( + *, + deprecated, + message: + "This initializer will be removed in the next minor version update. Please use `init(_ body: @escaping ([JSValue]) -> JSValue)` and add `return .undefined` to the end of your closure" + ) @_disfavoredOverload - public convenience init(_ body: @escaping ([JSValue]) -> ()) { + public convenience init(_ body: @escaping ([JSValue]) -> Void) { self.init({ body($0) return .undefined @@ -200,7 +209,8 @@ private func makeAsyncClosure( @_cdecl("_call_host_function_impl") func _call_host_function_impl( _ hostFuncRef: JavaScriptHostFuncRef, - _ argv: UnsafePointer, _ argc: Int32, + _ argv: UnsafePointer, + _ argc: Int32, _ callbackFuncRef: JavaScriptObjectRef ) -> Bool { guard let (_, hostFunc) = JSClosure.sharedClosures.wrappedValue[hostFuncRef] else { @@ -216,7 +226,6 @@ func _call_host_function_impl( return false } - /// [WeakRefs](https://github.com/tc39/proposal-weakrefs) are already Stage 4, /// but was added recently enough that older browser versions don’t support it. /// Build with `-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS` to disable the relevant behavior. @@ -231,7 +240,6 @@ extension JSClosure { } } - @_cdecl("_free_host_function_impl") func _free_host_function_impl(_ hostFuncRef: JavaScriptHostFuncRef) {} @@ -254,11 +262,13 @@ func _free_host_function_impl(_ hostFuncRef: JavaScriptHostFuncRef) { // cdecls currently don't work in embedded, and expose for wasm only works >=6.0 @_expose(wasm, "swjs_call_host_function") public func _swjs_call_host_function( - _ hostFuncRef: JavaScriptHostFuncRef, - _ argv: UnsafePointer, _ argc: Int32, - _ callbackFuncRef: JavaScriptObjectRef) -> Bool { + _ hostFuncRef: JavaScriptHostFuncRef, + _ argv: UnsafePointer, + _ argc: Int32, + _ callbackFuncRef: JavaScriptObjectRef +) -> Bool { - _call_host_function_impl(hostFuncRef, argv, argc, callbackFuncRef) + _call_host_function_impl(hostFuncRef, argv, argc, callbackFuncRef) } @_expose(wasm, "swjs_free_host_function") diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift b/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift index 498bbc3ea..172483612 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift @@ -11,7 +11,7 @@ import _CJavaScriptKit /// ``` /// public class JSFunction: JSObject { -#if !hasFeature(Embedded) + #if !hasFeature(Embedded) /// 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. @@ -84,7 +84,7 @@ public class JSFunction: JSObject { public var `throws`: JSThrowingFunction { JSThrowingFunction(self) } -#endif + #endif @discardableResult public func callAsFunction(arguments: [JSValue]) -> JSValue { @@ -116,7 +116,7 @@ public class JSFunction: JSObject { arguments.withRawJSValues { invokeNonThrowingJSFunction(rawValues: $0, this: this) } } -#if !hasFeature(Embedded) + #if !hasFeature(Embedded) final func invokeNonThrowingJSFunction(arguments: [ConvertibleToJSValue]) -> RawJSValue { arguments.withRawJSValues { invokeNonThrowingJSFunction(rawValues: $0) } } @@ -124,7 +124,7 @@ public class JSFunction: JSObject { final func invokeNonThrowingJSFunction(arguments: [ConvertibleToJSValue], this: JSObject) -> RawJSValue { arguments.withRawJSValues { invokeNonThrowingJSFunction(rawValues: $0, this: this) } } -#endif + #endif final private func invokeNonThrowingJSFunction(rawValues: [RawJSValue]) -> RawJSValue { rawValues.withUnsafeBufferPointer { [id] bufferPointer in @@ -133,8 +133,11 @@ public class JSFunction: JSObject { var payload1 = JavaScriptPayload1() var payload2 = JavaScriptPayload2() let resultBitPattern = swjs_call_function_no_catch( - id, argv, Int32(argc), - &payload1, &payload2 + id, + argv, + Int32(argc), + &payload1, + &payload2 ) let kindAndFlags = JavaScriptValueKindAndFlags(bitPattern: resultBitPattern) assert(!kindAndFlags.isException) @@ -149,9 +152,13 @@ public class JSFunction: JSObject { let argc = bufferPointer.count var payload1 = JavaScriptPayload1() var payload2 = JavaScriptPayload2() - let resultBitPattern = swjs_call_function_with_this_no_catch(this.id, - id, argv, Int32(argc), - &payload1, &payload2 + let resultBitPattern = swjs_call_function_with_this_no_catch( + this.id, + id, + argv, + Int32(argc), + &payload1, + &payload2 ) let kindAndFlags = JavaScriptValueKindAndFlags(bitPattern: resultBitPattern) #if !hasFeature(Embedded) @@ -172,20 +179,20 @@ public class JSFunction: JSObject { // // Once Embedded Swift supports parameter packs/variadic generics, we can // replace all variants with a single method each that takes a generic pack. -public extension JSFunction { +extension JSFunction { @discardableResult - func callAsFunction(this: JSObject) -> JSValue { + public func callAsFunction(this: JSObject) -> JSValue { invokeNonThrowingJSFunction(arguments: [], this: this).jsValue } @discardableResult - func callAsFunction(this: JSObject, _ arg0: some ConvertibleToJSValue) -> JSValue { + public func callAsFunction(this: JSObject, _ arg0: some ConvertibleToJSValue) -> JSValue { invokeNonThrowingJSFunction(arguments: [arg0.jsValue], this: this).jsValue } @discardableResult - func callAsFunction( + public func callAsFunction( this: JSObject, _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue @@ -194,7 +201,7 @@ public extension JSFunction { } @discardableResult - func callAsFunction( + public func callAsFunction( this: JSObject, _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue, @@ -204,18 +211,19 @@ public extension JSFunction { } @discardableResult - func callAsFunction( + public func callAsFunction( this: JSObject, _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue, _ arg2: some ConvertibleToJSValue, _ arg3: some ConvertibleToJSValue ) -> JSValue { - invokeNonThrowingJSFunction(arguments: [arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue], this: this).jsValue + invokeNonThrowingJSFunction(arguments: [arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue], this: this) + .jsValue } @discardableResult - func callAsFunction( + public func callAsFunction( this: JSObject, _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue, @@ -223,11 +231,14 @@ public extension JSFunction { _ arg3: some ConvertibleToJSValue, _ arg4: some ConvertibleToJSValue ) -> JSValue { - invokeNonThrowingJSFunction(arguments: [arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue, arg4.jsValue], this: this).jsValue + invokeNonThrowingJSFunction( + arguments: [arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue, arg4.jsValue], + this: this + ).jsValue } @discardableResult - func callAsFunction( + public func callAsFunction( this: JSObject, _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue, @@ -236,11 +247,14 @@ public extension JSFunction { _ arg4: some ConvertibleToJSValue, _ arg5: some ConvertibleToJSValue ) -> JSValue { - invokeNonThrowingJSFunction(arguments: [arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue, arg4.jsValue, arg5.jsValue], this: this).jsValue + invokeNonThrowingJSFunction( + arguments: [arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue, arg4.jsValue, arg5.jsValue], + this: this + ).jsValue } @discardableResult - func callAsFunction( + public func callAsFunction( this: JSObject, _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue, @@ -250,26 +264,31 @@ public extension JSFunction { _ arg5: some ConvertibleToJSValue, _ arg6: some ConvertibleToJSValue ) -> JSValue { - invokeNonThrowingJSFunction(arguments: [arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue, arg4.jsValue, arg5.jsValue, arg6.jsValue], this: this).jsValue + invokeNonThrowingJSFunction( + arguments: [ + arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue, arg4.jsValue, arg5.jsValue, arg6.jsValue, + ], + this: this + ).jsValue } @discardableResult - func callAsFunction(this: JSObject, arguments: [JSValue]) -> JSValue { + public func callAsFunction(this: JSObject, arguments: [JSValue]) -> JSValue { invokeNonThrowingJSFunction(arguments: arguments, this: this).jsValue } @discardableResult - func callAsFunction() -> JSValue { + public func callAsFunction() -> JSValue { invokeNonThrowingJSFunction(arguments: []).jsValue } @discardableResult - func callAsFunction(_ arg0: some ConvertibleToJSValue) -> JSValue { + public func callAsFunction(_ arg0: some ConvertibleToJSValue) -> JSValue { invokeNonThrowingJSFunction(arguments: [arg0.jsValue]).jsValue } @discardableResult - func callAsFunction( + public func callAsFunction( _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue ) -> JSValue { @@ -277,7 +296,7 @@ public extension JSFunction { } @discardableResult - func callAsFunction( + public func callAsFunction( _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue, _ arg2: some ConvertibleToJSValue @@ -286,7 +305,7 @@ public extension JSFunction { } @discardableResult - func callAsFunction( + public func callAsFunction( _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue, _ arg2: some ConvertibleToJSValue, @@ -296,18 +315,19 @@ public extension JSFunction { } @discardableResult - func callAsFunction( + public func callAsFunction( _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue, _ arg2: some ConvertibleToJSValue, _ arg3: some ConvertibleToJSValue, _ arg4: some ConvertibleToJSValue ) -> JSValue { - invokeNonThrowingJSFunction(arguments: [arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue, arg4.jsValue]).jsValue + invokeNonThrowingJSFunction(arguments: [arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue, arg4.jsValue]) + .jsValue } @discardableResult - func callAsFunction( + public func callAsFunction( _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue, _ arg2: some ConvertibleToJSValue, @@ -315,11 +335,13 @@ public extension JSFunction { _ arg4: some ConvertibleToJSValue, _ arg5: some ConvertibleToJSValue ) -> JSValue { - invokeNonThrowingJSFunction(arguments: [arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue, arg4.jsValue, arg5.jsValue]).jsValue + invokeNonThrowingJSFunction(arguments: [ + arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue, arg4.jsValue, arg5.jsValue, + ]).jsValue } @discardableResult - func callAsFunction( + public func callAsFunction( _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue, _ arg2: some ConvertibleToJSValue, @@ -328,25 +350,27 @@ public extension JSFunction { _ arg5: some ConvertibleToJSValue, _ arg6: some ConvertibleToJSValue ) -> JSValue { - invokeNonThrowingJSFunction(arguments: [arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue, arg4.jsValue, arg5.jsValue, arg6.jsValue]).jsValue + invokeNonThrowingJSFunction(arguments: [ + arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue, arg4.jsValue, arg5.jsValue, arg6.jsValue, + ]).jsValue } - func new() -> JSObject { + public func new() -> JSObject { new(arguments: []) } - func new(_ arg0: some ConvertibleToJSValue) -> JSObject { + public func new(_ arg0: some ConvertibleToJSValue) -> JSObject { new(arguments: [arg0.jsValue]) } - func new( + public func new( _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue ) -> JSObject { new(arguments: [arg0.jsValue, arg1.jsValue]) } - func new( + public func new( _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue, _ arg2: some ConvertibleToJSValue @@ -354,7 +378,7 @@ public extension JSFunction { new(arguments: [arg0.jsValue, arg1.jsValue, arg2.jsValue]) } - func new( + public func new( _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue, _ arg2: some ConvertibleToJSValue, @@ -363,7 +387,7 @@ public extension JSFunction { new(arguments: [arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue]) } - func new( + public func new( _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue, _ arg2: some ConvertibleToJSValue, @@ -373,7 +397,7 @@ public extension JSFunction { new(arguments: [arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue, arg4.jsValue]) } - func new( + public func new( _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue, _ arg2: some ConvertibleToJSValue, @@ -384,7 +408,7 @@ public extension JSFunction { new(arguments: [arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue, arg4.jsValue, arg5.jsValue]) } - func new( + public func new( _ arg0: some ConvertibleToJSValue, _ arg1: some ConvertibleToJSValue, _ arg2: some ConvertibleToJSValue, @@ -393,7 +417,9 @@ public extension JSFunction { _ arg5: some ConvertibleToJSValue, _ arg6: some ConvertibleToJSValue ) -> JSObject { - new(arguments: [arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue, arg4.jsValue, arg5.jsValue, arg6.jsValue]) + new(arguments: [ + arg0.jsValue, arg1.jsValue, arg2.jsValue, arg3.jsValue, arg4.jsValue, arg5.jsValue, arg6.jsValue, + ]) } } #endif diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift index 9006ec7b7..33a20f3b5 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift @@ -22,9 +22,9 @@ public class JSObject: Equatable { @usableFromInline internal var _id: JavaScriptObjectRef -#if compiler(>=6.1) && _runtime(_multithreaded) + #if compiler(>=6.1) && _runtime(_multithreaded) package let ownerTid: Int32 -#endif + #endif @_spi(JSObject_id) @inlinable @@ -33,9 +33,9 @@ public class JSObject: Equatable { @_spi(JSObject_id) public init(id: JavaScriptObjectRef) { self._id = id -#if compiler(>=6.1) && _runtime(_multithreaded) + #if compiler(>=6.1) && _runtime(_multithreaded) self.ownerTid = swjs_get_worker_thread_id_cached() -#endif + #endif } /// Asserts that the object is being accessed from the owner thread. @@ -47,18 +47,24 @@ public class JSObject: Equatable { /// object spaces are not shared across threads backed by Web Workers. private func assertOnOwnerThread(hint: @autoclosure () -> String) { #if compiler(>=6.1) && _runtime(_multithreaded) - precondition(ownerTid == swjs_get_worker_thread_id_cached(), "JSObject is being accessed from a thread other than the owner thread: \(hint())") + precondition( + ownerTid == swjs_get_worker_thread_id_cached(), + "JSObject is being accessed from a thread other than the owner thread: \(hint())" + ) #endif } /// Asserts that the two objects being compared are owned by the same thread. private static func assertSameOwnerThread(lhs: JSObject, rhs: JSObject, hint: @autoclosure () -> String) { #if compiler(>=6.1) && _runtime(_multithreaded) - precondition(lhs.ownerTid == rhs.ownerTid, "JSObject is being accessed from a thread other than the owner thread: \(hint())") + precondition( + lhs.ownerTid == rhs.ownerTid, + "JSObject is being accessed from a thread other than the owner thread: \(hint())" + ) #endif } -#if !hasFeature(Embedded) + #if !hasFeature(Embedded) /// Returns the `name` member method binding this object as `this` context. /// /// e.g. @@ -101,7 +107,7 @@ public class JSObject: Equatable { public subscript(dynamicMember name: String) -> ((ConvertibleToJSValue...) -> JSValue)? { self[name] } -#endif + #endif /// A convenience method of `subscript(_ name: String) -> JSValue` /// to access the member through Dynamic Member Lookup. @@ -166,7 +172,7 @@ public class JSObject: Equatable { } } -#if !hasFeature(Embedded) + #if !hasFeature(Embedded) /// A modifier to call methods as throwing methods capturing `this` /// /// @@ -187,7 +193,7 @@ public class JSObject: Equatable { public var throwing: JSThrowingObject { JSThrowingObject(self) } -#endif + #endif /// Return `true` if this value is an instance of the passed `constructor` function. /// - Parameter constructor: The constructor function to check. @@ -230,10 +236,11 @@ public class JSObject: Equatable { public static func construct(from value: JSValue) -> Self? { switch value { case .boolean, - .string, - .number, - .null, - .undefined: return nil + .string, + .number, + .null, + .undefined: + return nil case .object(let object): return object as? Self case .function(let function): @@ -295,7 +302,6 @@ public class JSThrowingObject { } #endif - #if hasFeature(Embedded) // Overloads of `JSObject.subscript(_ name: String) -> ((ConvertibleToJSValue...) -> JSValue)?` // for 0 through 7 arguments for Embedded Swift. @@ -305,33 +311,33 @@ public class JSThrowingObject { // // NOTE: Once Embedded Swift supports parameter packs/variadic generics, we can // replace all of these with a single method that takes a generic pack. -public extension JSObject { +extension JSObject { @_disfavoredOverload - subscript(dynamicMember name: String) -> (() -> JSValue)? { - self[name].function.map { function in + public subscript(dynamicMember name: String) -> (() -> JSValue)? { + self[name].function.map { function in { function(this: self) } } } @_disfavoredOverload - subscript(dynamicMember name: String) -> ((A0) -> JSValue)? { - self[name].function.map { function in + public subscript(dynamicMember name: String) -> ((A0) -> JSValue)? { + self[name].function.map { function in { function(this: self, $0) } } } @_disfavoredOverload - subscript< + public subscript< A0: ConvertibleToJSValue, A1: ConvertibleToJSValue >(dynamicMember name: String) -> ((A0, A1) -> JSValue)? { - self[name].function.map { function in + self[name].function.map { function in { function(this: self, $0, $1) } } } @_disfavoredOverload - subscript< + public subscript< A0: ConvertibleToJSValue, A1: ConvertibleToJSValue, A2: ConvertibleToJSValue @@ -342,7 +348,7 @@ public extension JSObject { } @_disfavoredOverload - subscript< + public subscript< A0: ConvertibleToJSValue, A1: ConvertibleToJSValue, A2: ConvertibleToJSValue, @@ -354,7 +360,7 @@ public extension JSObject { } @_disfavoredOverload - subscript< + public subscript< A0: ConvertibleToJSValue, A1: ConvertibleToJSValue, A2: ConvertibleToJSValue, @@ -367,7 +373,7 @@ public extension JSObject { } @_disfavoredOverload - subscript< + public subscript< A0: ConvertibleToJSValue, A1: ConvertibleToJSValue, A2: ConvertibleToJSValue, @@ -381,7 +387,7 @@ public extension JSObject { } @_disfavoredOverload - subscript< + public subscript< A0: ConvertibleToJSValue, A1: ConvertibleToJSValue, A2: ConvertibleToJSValue, diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSString.swift b/Sources/JavaScriptKit/FundamentalObjects/JSString.swift index 686d1ba11..cd88a5302 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSString.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSString.swift @@ -66,7 +66,7 @@ public struct JSString: LosslessStringConvertible, Equatable { public init(_ stringValue: String) { self.guts = Guts(from: stringValue) } - + /// A Swift representation of this `JSString`. /// Note that this accessor may copy the JS string value into Swift side memory. public var description: String { guts.buffer } @@ -87,7 +87,6 @@ extension JSString: ExpressibleByStringLiteral { } } - // MARK: - Internal Helpers extension JSString { @@ -97,7 +96,9 @@ extension JSString { func withRawJSValue(_ body: (RawJSValue) -> T) -> T { let rawValue = RawJSValue( - kind: .string, payload1: guts.jsRef, payload2: 0 + kind: .string, + payload1: guts.jsRef, + payload2: 0 ) return body(rawValue) } diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSThrowingFunction.swift b/Sources/JavaScriptKit/FundamentalObjects/JSThrowingFunction.swift index 17b61090f..aee17fd69 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSThrowingFunction.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSThrowingFunction.swift @@ -1,7 +1,6 @@ #if !hasFeature(Embedded) import _CJavaScriptKit - /// A `JSFunction` wrapper that enables throwing function calls. /// Exceptions produced by JavaScript functions will be thrown as `JSValue`. public class JSThrowingFunction { @@ -46,12 +45,20 @@ public class JSThrowingFunction { var exceptionPayload1 = JavaScriptPayload1() var exceptionPayload2 = JavaScriptPayload2() let resultObj = swjs_call_throwing_new( - self.base.id, argv, Int32(argc), - &exceptionRawKind, &exceptionPayload1, &exceptionPayload2 + self.base.id, + argv, + Int32(argc), + &exceptionRawKind, + &exceptionPayload1, + &exceptionPayload2 ) let exceptionKind = JavaScriptValueKindAndFlags(bitPattern: exceptionRawKind) if exceptionKind.isException { - let exception = RawJSValue(kind: exceptionKind.kind, payload1: exceptionPayload1, payload2: exceptionPayload2) + let exception = RawJSValue( + kind: exceptionKind.kind, + payload1: exceptionPayload1, + payload2: exceptionPayload2 + ) return .failure(JSException(exception.jsValue)) } return .success(JSObject(id: resultObj)) @@ -65,7 +72,11 @@ public class JSThrowingFunction { } } -private func invokeJSFunction(_ jsFunc: JSFunction, arguments: [ConvertibleToJSValue], this: JSObject?) throws -> JSValue { +private func invokeJSFunction( + _ jsFunc: JSFunction, + arguments: [ConvertibleToJSValue], + this: JSObject? +) throws -> JSValue { let id = jsFunc.id let (result, isException) = arguments.withRawJSValues { rawValues in rawValues.withUnsafeBufferPointer { bufferPointer -> (JSValue, Bool) in @@ -76,14 +87,21 @@ private func invokeJSFunction(_ jsFunc: JSFunction, arguments: [ConvertibleToJSV var payload2 = JavaScriptPayload2() if let thisId = this?.id { let resultBitPattern = swjs_call_function_with_this( - thisId, id, argv, Int32(argc), - &payload1, &payload2 + thisId, + id, + argv, + Int32(argc), + &payload1, + &payload2 ) kindAndFlags = JavaScriptValueKindAndFlags(bitPattern: resultBitPattern) } else { let resultBitPattern = swjs_call_function( - id, argv, Int32(argc), - &payload1, &payload2 + id, + argv, + Int32(argc), + &payload1, + &payload2 ) kindAndFlags = JavaScriptValueKindAndFlags(bitPattern: resultBitPattern) } diff --git a/Sources/JavaScriptKit/JSBridgedType.swift b/Sources/JavaScriptKit/JSBridgedType.swift index dcc0a3857..92739079e 100644 --- a/Sources/JavaScriptKit/JSBridgedType.swift +++ b/Sources/JavaScriptKit/JSBridgedType.swift @@ -5,12 +5,12 @@ public protocol JSBridgedType: JSValueCompatible, CustomStringConvertible { init?(from value: JSValue) } -public extension JSBridgedType { - static func construct(from value: JSValue) -> Self? { +extension JSBridgedType { + public static func construct(from value: JSValue) -> Self? { Self(from: value) } - var description: String { jsValue.description } + public var description: String { jsValue.description } } /// Conform to this protocol when your Swift class wraps a JavaScript class. @@ -27,15 +27,15 @@ public protocol JSBridgedClass: JSBridgedType { init(unsafelyWrapping jsObject: JSObject) } -public extension JSBridgedClass { - var jsValue: JSValue { jsObject.jsValue } +extension JSBridgedClass { + public var jsValue: JSValue { jsObject.jsValue } - init?(from value: JSValue) { + public init?(from value: JSValue) { guard let object = value.object else { return nil } self.init(from: object) } - init?(from object: JSObject) { + public init?(from object: JSObject) { guard let constructor = Self.constructor, object.isInstanceOf(constructor) else { return nil } self.init(unsafelyWrapping: object) } diff --git a/Sources/JavaScriptKit/JSException.swift b/Sources/JavaScriptKit/JSException.swift index 393ae9615..8783d808b 100644 --- a/Sources/JavaScriptKit/JSException.swift +++ b/Sources/JavaScriptKit/JSException.swift @@ -16,7 +16,7 @@ public struct JSException: Error, Equatable { /// The value thrown from JavaScript. /// This can be any JavaScript value (error object, string, number, etc.). public var thrownValue: JSValue { - return _thrownValue + return _thrownValue } /// The actual JavaScript value that was thrown. diff --git a/Sources/JavaScriptKit/JSValue.swift b/Sources/JavaScriptKit/JSValue.swift index 2562daac8..8c605530f 100644 --- a/Sources/JavaScriptKit/JSValue.swift +++ b/Sources/JavaScriptKit/JSValue.swift @@ -17,7 +17,7 @@ public enum JSValue: Equatable { /// If not, returns `nil`. public var boolean: Bool? { switch self { - case let .boolean(boolean): return boolean + case .boolean(let boolean): return boolean default: return nil } } @@ -37,7 +37,7 @@ public enum JSValue: Equatable { /// public var jsString: JSString? { switch self { - case let .string(string): return string + case .string(let string): return string default: return nil } } @@ -46,7 +46,7 @@ public enum JSValue: Equatable { /// If not, returns `nil`. public var number: Double? { switch self { - case let .number(number): return number + case .number(let number): return number default: return nil } } @@ -55,7 +55,7 @@ public enum JSValue: Equatable { /// If not, returns `nil`. public var object: JSObject? { switch self { - case let .object(object): return object + case .object(let object): return object default: return nil } } @@ -64,7 +64,7 @@ public enum JSValue: Equatable { /// If not, returns `nil`. public var function: JSFunction? { switch self { - case let .function(function): return function + case .function(let function): return function default: return nil } } @@ -73,7 +73,7 @@ public enum JSValue: Equatable { /// If not, returns `nil`. public var symbol: JSSymbol? { switch self { - case let .symbol(symbol): return symbol + case .symbol(let symbol): return symbol default: return nil } } @@ -82,7 +82,7 @@ public enum JSValue: Equatable { /// If not, returns `nil`. public var bigInt: JSBigInt? { switch self { - case let .bigInt(bigInt): return bigInt + case .bigInt(let bigInt): return bigInt default: return nil } } @@ -107,38 +107,38 @@ public enum JSValue: Equatable { @available(*, unavailable) extension JSValue: Sendable {} -public extension JSValue { -#if !hasFeature(Embedded) +extension JSValue { + #if !hasFeature(Embedded) /// An unsafe convenience method of `JSObject.subscript(_ name: String) -> ((ConvertibleToJSValue...) -> JSValue)?` /// - Precondition: `self` must be a JavaScript Object and specified member should be a callable object. - subscript(dynamicMember name: String) -> ((ConvertibleToJSValue...) -> JSValue) { + public subscript(dynamicMember name: String) -> ((ConvertibleToJSValue...) -> JSValue) { object![dynamicMember: name]! } -#endif + #endif /// An unsafe convenience method of `JSObject.subscript(_ index: Int) -> JSValue` /// - Precondition: `self` must be a JavaScript Object. - subscript(dynamicMember name: String) -> JSValue { + public subscript(dynamicMember name: String) -> JSValue { get { self.object![name] } set { self.object![name] = newValue } } /// An unsafe convenience method of `JSObject.subscript(_ index: Int) -> JSValue` /// - Precondition: `self` must be a JavaScript Object. - subscript(_ index: Int) -> JSValue { + public subscript(_ index: Int) -> JSValue { get { object![index] } set { object![index] = newValue } } } -public extension JSValue { - func fromJSValue() -> Type? where Type: ConstructibleFromJSValue { +extension JSValue { + public func fromJSValue() -> Type? where Type: ConstructibleFromJSValue { return Type.construct(from: self) } } -public extension JSValue { - static func string(_ value: String) -> JSValue { +extension JSValue { + public static func string(_ value: String) -> JSValue { .string(JSString(value)) } @@ -167,12 +167,17 @@ public extension JSValue { /// eventListener.release() /// ``` @available(*, deprecated, message: "Please create JSClosure directly and manage its lifetime manually.") - static func function(_ body: @escaping ([JSValue]) -> JSValue) -> JSValue { + public static func function(_ body: @escaping ([JSValue]) -> JSValue) -> JSValue { .object(JSClosure(body)) } - @available(*, deprecated, renamed: "object", message: "JSClosure is no longer a subclass of JSFunction. Use .object(closure) instead.") - static func function(_ closure: JSClosure) -> JSValue { + @available( + *, + deprecated, + renamed: "object", + message: "JSClosure is no longer a subclass of JSFunction. Use .object(closure) instead." + ) + public static func function(_ closure: JSClosure) -> JSValue { .object(closure) } } @@ -204,8 +209,10 @@ extension JSValue: ExpressibleByNilLiteral { public func getJSValue(this: JSObject, name: JSString) -> JSValue { var rawValue = RawJSValue() let rawBitPattern = swjs_get_prop( - this.id, name.asInternalJSRef(), - &rawValue.payload1, &rawValue.payload2 + this.id, + name.asInternalJSRef(), + &rawValue.payload1, + &rawValue.payload2 ) rawValue.kind = unsafeBitCast(rawBitPattern, to: JavaScriptValueKind.self) return rawValue.jsValue @@ -220,8 +227,10 @@ public func setJSValue(this: JSObject, name: JSString, value: JSValue) { public func getJSValue(this: JSObject, index: Int32) -> JSValue { var rawValue = RawJSValue() let rawBitPattern = swjs_get_subscript( - this.id, index, - &rawValue.payload1, &rawValue.payload2 + this.id, + index, + &rawValue.payload1, + &rawValue.payload2 ) rawValue.kind = unsafeBitCast(rawBitPattern, to: JavaScriptValueKind.self) return rawValue.jsValue @@ -229,17 +238,23 @@ public func getJSValue(this: JSObject, index: Int32) -> JSValue { public func setJSValue(this: JSObject, index: Int32, value: JSValue) { value.withRawJSValue { rawValue in - swjs_set_subscript(this.id, index, - rawValue.kind, - rawValue.payload1, rawValue.payload2) + swjs_set_subscript( + this.id, + index, + rawValue.kind, + rawValue.payload1, + rawValue.payload2 + ) } } public func getJSValue(this: JSObject, symbol: JSSymbol) -> JSValue { var rawValue = RawJSValue() let rawBitPattern = swjs_get_prop( - this.id, symbol.id, - &rawValue.payload1, &rawValue.payload2 + this.id, + symbol.id, + &rawValue.payload1, + &rawValue.payload2 ) rawValue.kind = unsafeBitCast(rawBitPattern, to: JavaScriptValueKind.self) return rawValue.jsValue @@ -251,18 +266,18 @@ public func setJSValue(this: JSObject, symbol: JSSymbol, value: JSValue) { } } -public extension JSValue { +extension JSValue { /// Return `true` if this value is an instance of the passed `constructor` function. /// Returns `false` for everything except objects and functions. /// - Parameter constructor: The constructor function to check. /// - Returns: The result of `instanceof` in the JavaScript environment. - func isInstanceOf(_ constructor: JSFunction) -> Bool { + public func isInstanceOf(_ constructor: JSFunction) -> Bool { switch self { case .boolean, .string, .number, .null, .undefined, .symbol, .bigInt: return false - case let .object(ref): + case .object(let ref): return ref.isInstanceOf(constructor) - case let .function(ref): + case .function(let ref): return ref.isInstanceOf(constructor) } } @@ -285,19 +300,19 @@ extension JSValue: CustomStringConvertible { // // Note: Once Embedded Swift supports parameter packs/variadic generics, we can // replace all of these with a single method that takes a generic pack. -public extension JSValue { +extension JSValue { @_disfavoredOverload - subscript(dynamicMember name: String) -> (() -> JSValue) { + public subscript(dynamicMember name: String) -> (() -> JSValue) { object![dynamicMember: name]! } @_disfavoredOverload - subscript(dynamicMember name: String) -> ((A0) -> JSValue) { + public subscript(dynamicMember name: String) -> ((A0) -> JSValue) { object![dynamicMember: name]! } @_disfavoredOverload - subscript< + public subscript< A0: ConvertibleToJSValue, A1: ConvertibleToJSValue >(dynamicMember name: String) -> ((A0, A1) -> JSValue) { @@ -305,7 +320,7 @@ public extension JSValue { } @_disfavoredOverload - subscript< + public subscript< A0: ConvertibleToJSValue, A1: ConvertibleToJSValue, A2: ConvertibleToJSValue @@ -314,7 +329,7 @@ public extension JSValue { } @_disfavoredOverload - subscript< + public subscript< A0: ConvertibleToJSValue, A1: ConvertibleToJSValue, A2: ConvertibleToJSValue, @@ -324,7 +339,7 @@ public extension JSValue { } @_disfavoredOverload - subscript< + public subscript< A0: ConvertibleToJSValue, A1: ConvertibleToJSValue, A2: ConvertibleToJSValue, @@ -335,7 +350,7 @@ public extension JSValue { } @_disfavoredOverload - subscript< + public subscript< A0: ConvertibleToJSValue, A1: ConvertibleToJSValue, A2: ConvertibleToJSValue, @@ -347,7 +362,7 @@ public extension JSValue { } @_disfavoredOverload - subscript< + public subscript< A0: ConvertibleToJSValue, A1: ConvertibleToJSValue, A2: ConvertibleToJSValue, diff --git a/Sources/JavaScriptKit/JSValueDecoder.swift b/Sources/JavaScriptKit/JSValueDecoder.swift index b2cf7b2a3..08e29915c 100644 --- a/Sources/JavaScriptKit/JSValueDecoder.swift +++ b/Sources/JavaScriptKit/JSValueDecoder.swift @@ -121,7 +121,10 @@ private struct _KeyedDecodingContainer: KeyedDecodingContainerPr return try T(from: _decoder(forKey: key)) } - func nestedContainer(keyedBy _: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey: CodingKey { + func nestedContainer( + keyedBy _: NestedKey.Type, + forKey key: Key + ) throws -> KeyedDecodingContainer where NestedKey: CodingKey { try _decoder(forKey: key).container(keyedBy: NestedKey.self) } @@ -185,7 +188,8 @@ private struct _UnkeyedDecodingContainer: UnkeyedDecodingContainer { return try T(from: _decoder()) } - mutating func nestedContainer(keyedBy _: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey: CodingKey { + mutating func nestedContainer(keyedBy _: NestedKey.Type) throws -> KeyedDecodingContainer + where NestedKey: CodingKey { return try _decoder().container(keyedBy: NestedKey.self) } diff --git a/Sources/JavaScriptKit/ThreadLocal.swift b/Sources/JavaScriptKit/ThreadLocal.swift index 9f5751c96..e92ca32ac 100644 --- a/Sources/JavaScriptKit/ThreadLocal.swift +++ b/Sources/JavaScriptKit/ThreadLocal.swift @@ -15,7 +15,7 @@ import Glibc /// The value is stored in a thread-local variable, which is a separate copy for each thread. @propertyWrapper final class ThreadLocal: Sendable { -#if compiler(>=6.1) && _runtime(_multithreaded) + #if compiler(>=6.1) && _runtime(_multithreaded) /// The wrapped value stored in the thread-local storage. /// The initial value is `nil` for each thread. var wrappedValue: Value? { @@ -76,7 +76,7 @@ final class ThreadLocal: Sendable { } self.release = { Unmanaged.fromOpaque($0).release() } } -#else + #else // Fallback implementation for platforms that don't support pthread private class SendableBox: @unchecked Sendable { var value: Value? = nil @@ -93,7 +93,7 @@ final class ThreadLocal: Sendable { init(boxing _: Void) { wrappedValue = nil } -#endif + #endif deinit { preconditionFailure("ThreadLocal can only be used as an immortal storage, cannot be deallocated") diff --git a/Tests/JavaScriptBigIntSupportTests/JavaScriptBigIntSupportTests.swift b/Tests/JavaScriptBigIntSupportTests/JavaScriptBigIntSupportTests.swift index e1fb8a96f..73aacf1bb 100644 --- a/Tests/JavaScriptBigIntSupportTests/JavaScriptBigIntSupportTests.swift +++ b/Tests/JavaScriptBigIntSupportTests/JavaScriptBigIntSupportTests.swift @@ -1,6 +1,6 @@ -import XCTest import JavaScriptBigIntSupport import JavaScriptKit +import XCTest class JavaScriptBigIntSupportTests: XCTestCase { func testBigIntSupport() { @@ -11,7 +11,7 @@ class JavaScriptBigIntSupportTests: XCTestCase { let bigInt2 = JSBigInt(_slowBridge: value) XCTAssertEqual(bigInt2.description, value.description, file: file, line: line) } - + // Test unsigned values func testUnsignedValue(_ value: UInt64, file: StaticString = #filePath, line: UInt = #line) { let bigInt = JSBigInt(unsigned: value) @@ -19,21 +19,21 @@ class JavaScriptBigIntSupportTests: XCTestCase { let bigInt2 = JSBigInt(_slowBridge: value) XCTAssertEqual(bigInt2.description, value.description, file: file, line: line) } - + // Test specific signed values testSignedValue(0) testSignedValue(1 << 62) testSignedValue(-2305) - + // Test random signed values for _ in 0..<100 { testSignedValue(.random(in: .min ... .max)) } - + // Test edge signed values testSignedValue(.min) testSignedValue(.max) - + // Test specific unsigned values testUnsignedValue(0) testUnsignedValue(1 << 62) @@ -41,7 +41,7 @@ class JavaScriptBigIntSupportTests: XCTestCase { testUnsignedValue(.min) testUnsignedValue(.max) testUnsignedValue(~0) - + // Test random unsigned values for _ in 0..<100 { testUnsignedValue(.random(in: .min ... .max)) diff --git a/Tests/JavaScriptEventLoopTestSupportTests/JavaScriptEventLoopTestSupportTests.swift b/Tests/JavaScriptEventLoopTestSupportTests/JavaScriptEventLoopTestSupportTests.swift index cca303a09..d2b776304 100644 --- a/Tests/JavaScriptEventLoopTestSupportTests/JavaScriptEventLoopTestSupportTests.swift +++ b/Tests/JavaScriptEventLoopTestSupportTests/JavaScriptEventLoopTestSupportTests.swift @@ -1,5 +1,5 @@ -import XCTest import JavaScriptKit +import XCTest final class JavaScriptEventLoopTestSupportTests: XCTestCase { func testAwaitMicrotask() async { diff --git a/Tests/JavaScriptEventLoopTests/JSPromiseTests.swift b/Tests/JavaScriptEventLoopTests/JSPromiseTests.swift index e19d356e5..962b04421 100644 --- a/Tests/JavaScriptEventLoopTests/JSPromiseTests.swift +++ b/Tests/JavaScriptEventLoopTests/JSPromiseTests.swift @@ -1,4 +1,5 @@ import XCTest + @testable import JavaScriptKit final class JSPromiseTests: XCTestCase { diff --git a/Tests/JavaScriptEventLoopTests/JavaScriptEventLoopTests.swift b/Tests/JavaScriptEventLoopTests/JavaScriptEventLoopTests.swift index 1cd628338..1da56e680 100644 --- a/Tests/JavaScriptEventLoopTests/JavaScriptEventLoopTests.swift +++ b/Tests/JavaScriptEventLoopTests/JavaScriptEventLoopTests.swift @@ -18,7 +18,9 @@ final class JavaScriptEventLoopTests: XCTestCase { } func expectAsyncThrow( - _ body: @autoclosure () async throws -> T, file: StaticString = #file, line: UInt = #line, + _ body: @autoclosure () async throws -> T, + file: StaticString = #file, + line: UInt = #line, column: UInt = #column ) async throws -> Error { do { @@ -241,20 +243,20 @@ final class JavaScriptEventLoopTests: XCTestCase { // MARK: - Clock Tests #if compiler(>=5.7) - func testClockSleep() async throws { - // Test ContinuousClock.sleep - let continuousClockDiff = try await measureTime { - let c = ContinuousClock() - try await c.sleep(until: .now + .milliseconds(100)) - } - XCTAssertGreaterThanOrEqual(continuousClockDiff, 50) + func testClockSleep() async throws { + // Test ContinuousClock.sleep + let continuousClockDiff = try await measureTime { + let c = ContinuousClock() + try await c.sleep(until: .now + .milliseconds(100)) + } + XCTAssertGreaterThanOrEqual(continuousClockDiff, 50) - // Test SuspendingClock.sleep - let suspendingClockDiff = try await measureTime { - let c = SuspendingClock() - try await c.sleep(until: .now + .milliseconds(100)) - } - XCTAssertGreaterThanOrEqual(suspendingClockDiff, 50) + // Test SuspendingClock.sleep + let suspendingClockDiff = try await measureTime { + let c = SuspendingClock() + try await c.sleep(until: .now + .milliseconds(100)) } + XCTAssertGreaterThanOrEqual(suspendingClockDiff, 50) + } #endif } diff --git a/Tests/JavaScriptEventLoopTests/WebWorkerTaskExecutorTests.swift b/Tests/JavaScriptEventLoopTests/WebWorkerTaskExecutorTests.swift index 1696224df..8a45aead5 100644 --- a/Tests/JavaScriptEventLoopTests/WebWorkerTaskExecutorTests.swift +++ b/Tests/JavaScriptEventLoopTests/WebWorkerTaskExecutorTests.swift @@ -1,6 +1,6 @@ #if compiler(>=6.1) && _runtime(_multithreaded) import XCTest -import _CJavaScriptKit // For swjs_get_worker_thread_id +import _CJavaScriptKit // For swjs_get_worker_thread_id @testable import JavaScriptKit @testable import JavaScriptEventLoop @@ -226,17 +226,17 @@ final class WebWorkerTaskExecutorTests: XCTestCase { func decodeJob() throws { let json = """ - { - "prop_1": { - "nested_prop": 42 - }, - "prop_2": 100, - "prop_3": true, - "prop_7": 3.14, - "prop_8": "Hello, World!", - "prop_9": ["a", "b", "c"] - } - """ + { + "prop_1": { + "nested_prop": 42 + }, + "prop_2": 100, + "prop_3": true, + "prop_7": 3.14, + "prop_8": "Hello, World!", + "prop_9": ["a", "b", "c"] + } + """ let object = JSObject.global.JSON.parse(json) let decoder = JSValueDecoder() let result = try decoder.decode(DecodeMe.self, from: object) @@ -444,18 +444,18 @@ final class WebWorkerTaskExecutorTests: XCTestCase { XCTAssertEqual(object["test"].string!, "Hello, World!") } -/* - func testDeinitJSObjectOnDifferentThread() async throws { - let executor = try await WebWorkerTaskExecutor(numberOfThreads: 1) + /* + func testDeinitJSObjectOnDifferentThread() async throws { + let executor = try await WebWorkerTaskExecutor(numberOfThreads: 1) - var object: JSObject? = JSObject.global.Object.function!.new() - let task = Task(executorPreference: executor) { - object = nil - _ = object + var object: JSObject? = JSObject.global.Object.function!.new() + let task = Task(executorPreference: executor) { + object = nil + _ = object + } + await task.value + executor.terminate() } - await task.value - executor.terminate() - } -*/ + */ } #endif diff --git a/Tests/JavaScriptKitTests/JavaScriptKitTests.swift b/Tests/JavaScriptKitTests/JavaScriptKitTests.swift index 6c90afead..d7911feb9 100644 --- a/Tests/JavaScriptKitTests/JavaScriptKitTests.swift +++ b/Tests/JavaScriptKitTests/JavaScriptKitTests.swift @@ -1,5 +1,5 @@ -import XCTest import JavaScriptKit +import XCTest class JavaScriptKitTests: XCTestCase { func testLiteralConversion() { @@ -22,7 +22,7 @@ class JavaScriptKitTests: XCTestCase { setJSValue(this: global, name: prop, value: input) let got = getJSValue(this: global, name: prop) switch (got, input) { - case let (.number(lhs), .number(rhs)): + case (.number(let lhs), .number(let rhs)): // Compare bitPattern because nan == nan is always false XCTAssertEqual(lhs.bitPattern, rhs.bitPattern) default: @@ -30,7 +30,7 @@ class JavaScriptKitTests: XCTestCase { } } } - + func testObjectConversion() { // Notes: globalObject1 is defined in JavaScript environment // @@ -47,7 +47,7 @@ class JavaScriptKitTests: XCTestCase { // ... // } // ``` - + let globalObject1 = getJSValue(this: .global, name: "globalObject1") let globalObject1Ref = try! XCTUnwrap(globalObject1.object) let prop_1 = getJSValue(this: globalObject1Ref, name: "prop_1") @@ -67,10 +67,10 @@ class JavaScriptKitTests: XCTestCase { let actualElement = getJSValue(this: prop_4Array, index: Int32(index)) XCTAssertEqual(actualElement, expectedElement) } - + XCTAssertEqual(getJSValue(this: globalObject1Ref, name: "undefined_prop"), .undefined) } - + func testValueConstruction() { let globalObject1 = getJSValue(this: .global, name: "globalObject1") let globalObject1Ref = try! XCTUnwrap(globalObject1.object) @@ -81,10 +81,10 @@ class JavaScriptKitTests: XCTestCase { let prop_7 = getJSValue(this: globalObject1Ref, name: "prop_7") XCTAssertEqual(Double.construct(from: prop_7), 3.14) XCTAssertEqual(Float.construct(from: prop_7), 3.14) - + for source: JSValue in [ .number(.infinity), .number(.nan), - .number(Double(UInt64.max).nextUp), .number(Double(Int64.min).nextDown) + .number(Double(UInt64.max).nextUp), .number(Double(Int64.min).nextDown), ] { XCTAssertNil(Int.construct(from: source)) XCTAssertNil(Int8.construct(from: source)) @@ -98,7 +98,7 @@ class JavaScriptKitTests: XCTestCase { XCTAssertNil(UInt64.construct(from: source)) } } - + func testArrayIterator() { let globalObject1 = getJSValue(this: .global, name: "globalObject1") let globalObject1Ref = try! XCTUnwrap(globalObject1.object) @@ -108,14 +108,14 @@ class JavaScriptKitTests: XCTestCase { .number(3), .number(4), .string("str_elm_1"), .null, .undefined, .number(5), ] XCTAssertEqual(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! XCTUnwrap(prop_8.array) let expectedProp_8: [JSValue] = [0, 2, 3, 6] XCTAssertEqual(Array(array2), expectedProp_8) } - + func testArrayRandomAccessCollection() { let globalObject1 = getJSValue(this: .global, name: "globalObject1") let globalObject1Ref = try! XCTUnwrap(globalObject1.object) @@ -125,22 +125,22 @@ class JavaScriptKitTests: XCTestCase { .number(3), .number(4), .string("str_elm_1"), .null, .undefined, .number(5), ] XCTAssertEqual([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! XCTUnwrap(prop_8.array) let expectedProp_8: [JSValue] = [ - 0, .undefined, 2, 3, .undefined, .undefined, 6 + 0, .undefined, 2, 3, .undefined, .undefined, 6, ] XCTAssertEqual([array2[0], array2[1], array2[2], array2[3], array2[4], array2[5], array2[6]], expectedProp_8) } - + func testValueDecoder() { struct GlobalObject1: Codable { struct Prop1: Codable { let nested_prop: Int } - + let prop_1: Prop1 let prop_2: Int let prop_3: Bool @@ -154,7 +154,7 @@ class JavaScriptKitTests: XCTestCase { XCTAssertEqual(globalObject1.prop_3, true) XCTAssertEqual(globalObject1.prop_7, 3.14) } - + func testFunctionCall() { // Notes: globalObject1 is defined in JavaScript environment // @@ -174,12 +174,12 @@ class JavaScriptKitTests: XCTestCase { // ... // } // ``` - + let globalObject1 = getJSValue(this: .global, name: "globalObject1") let globalObject1Ref = try! XCTUnwrap(globalObject1.object) let prop_5 = getJSValue(this: globalObject1Ref, name: "prop_5") let prop_5Ref = try! XCTUnwrap(prop_5.object) - + let func1 = try! XCTUnwrap(getJSValue(this: prop_5Ref, name: "func1").function) XCTAssertEqual(func1(), .undefined) let func2 = try! XCTUnwrap(getJSValue(this: prop_5Ref, name: "func2").function) @@ -196,30 +196,30 @@ class JavaScriptKitTests: XCTestCase { XCTAssertEqual(func6(false, 1, 2), .number(2)) XCTAssertEqual(func6(true, "OK", 2), .string("OK")) } - + func testClosureLifetime() { let evalClosure = JSObject.global.globalObject1.eval_closure.function! - + do { let c1 = JSClosure { arguments in return arguments[0] } XCTAssertEqual(evalClosure(c1, JSValue.number(1.0)), .number(1.0)) -#if JAVASCRIPTKIT_WITHOUT_WEAKREFS + #if JAVASCRIPTKIT_WITHOUT_WEAKREFS c1.release() -#endif + #endif } - + do { let array = JSObject.global.Array.function!.new() let c1 = JSClosure { _ in .number(3) } _ = array.push!(c1) XCTAssertEqual(array[0].function!().number, 3.0) -#if JAVASCRIPTKIT_WITHOUT_WEAKREFS + #if JAVASCRIPTKIT_WITHOUT_WEAKREFS c1.release() -#endif + #endif } - + do { let c1 = JSClosure { _ in .undefined } XCTAssertEqual(c1(), .undefined) @@ -230,7 +230,7 @@ class JavaScriptKitTests: XCTestCase { XCTAssertEqual(c1(), .number(4)) } } - + func testHostFunctionRegistration() { // ```js // global.globalObject1 = { @@ -246,24 +246,24 @@ class JavaScriptKitTests: XCTestCase { let globalObject1Ref = try! XCTUnwrap(globalObject1.object) let prop_6 = getJSValue(this: globalObject1Ref, name: "prop_6") let prop_6Ref = try! XCTUnwrap(prop_6.object) - + var isHostFunc1Called = false let hostFunc1 = JSClosure { (_) -> JSValue in isHostFunc1Called = true return .number(1) } - + setJSValue(this: prop_6Ref, name: "host_func_1", value: .object(hostFunc1)) - + let call_host_1 = getJSValue(this: prop_6Ref, name: "call_host_1") let call_host_1Func = try! XCTUnwrap(call_host_1.function) XCTAssertEqual(call_host_1Func(), .number(1)) XCTAssertEqual(isHostFunc1Called, true) - -#if JAVASCRIPTKIT_WITHOUT_WEAKREFS + + #if JAVASCRIPTKIT_WITHOUT_WEAKREFS hostFunc1.release() -#endif - + #endif + let evalClosure = JSObject.global.globalObject1.eval_closure.function! let hostFunc2 = JSClosure { (arguments) -> JSValue in if let input = arguments[0].number { @@ -272,15 +272,15 @@ class JavaScriptKitTests: XCTestCase { return .string(String(describing: arguments[0])) } } - + XCTAssertEqual(evalClosure(hostFunc2, 3), .number(6)) XCTAssertTrue(evalClosure(hostFunc2, true).string != nil) - -#if JAVASCRIPTKIT_WITHOUT_WEAKREFS + + #if JAVASCRIPTKIT_WITHOUT_WEAKREFS hostFunc2.release() -#endif + #endif } - + func testNewObjectConstruction() { // ```js // global.Animal = function(name, age, isCat) { @@ -299,14 +299,14 @@ class JavaScriptKitTests: XCTestCase { XCTAssertEqual(cat1.isInstanceOf(try! XCTUnwrap(getJSValue(this: .global, name: "Array").function)), false) let cat1Bark = try! XCTUnwrap(getJSValue(this: cat1, name: "bark").function) XCTAssertEqual(cat1Bark(), .string("nyan")) - + let dog1 = objectConstructor.new("Pochi", 3, false) let dog1Bark = try! XCTUnwrap(getJSValue(this: dog1, name: "bark").function) XCTAssertEqual(dog1Bark(), .string("wan")) } - + func testObjectDecoding() { - /* + /* ```js global.objectDecodingTest = { obj: {}, @@ -317,7 +317,7 @@ class JavaScriptKitTests: XCTestCase { ``` */ let js: JSValue = JSObject.global.objectDecodingTest - + // I can't use regular name like `js.object` here // cz its conflicting with case name and DML. // so I use abbreviated names @@ -325,28 +325,28 @@ class JavaScriptKitTests: XCTestCase { let function: JSValue = js.fn let symbol: JSValue = js.sym let bigInt: JSValue = js.bi - + XCTAssertNotNil(JSObject.construct(from: object)) XCTAssertEqual(JSObject.construct(from: function).map { $0 is JSFunction }, .some(true)) XCTAssertEqual(JSObject.construct(from: symbol).map { $0 is JSSymbol }, .some(true)) XCTAssertEqual(JSObject.construct(from: bigInt).map { $0 is JSBigInt }, .some(true)) - + XCTAssertNil(JSFunction.construct(from: object)) XCTAssertNotNil(JSFunction.construct(from: function)) XCTAssertNil(JSFunction.construct(from: symbol)) XCTAssertNil(JSFunction.construct(from: bigInt)) - + XCTAssertNil(JSSymbol.construct(from: object)) XCTAssertNil(JSSymbol.construct(from: function)) XCTAssertNotNil(JSSymbol.construct(from: symbol)) XCTAssertNil(JSSymbol.construct(from: bigInt)) - + XCTAssertNil(JSBigInt.construct(from: object)) XCTAssertNil(JSBigInt.construct(from: function)) XCTAssertNil(JSBigInt.construct(from: symbol)) XCTAssertNotNil(JSBigInt.construct(from: bigInt)) } - + func testCallFunctionWithThis() { // ```js // global.Animal = function(name, age, isCat) { @@ -366,16 +366,16 @@ class JavaScriptKitTests: XCTestCase { let cat1Value = JSValue.object(cat1) let getIsCat = try! XCTUnwrap(getJSValue(this: cat1, name: "getIsCat").function) let setName = try! XCTUnwrap(getJSValue(this: cat1, name: "setName").function) - + // Direct call without this XCTAssertThrowsError(try getIsCat.throws()) - + // Call with this let gotIsCat = getIsCat(this: cat1) XCTAssertEqual(gotIsCat, .boolean(true)) XCTAssertEqual(cat1.getIsCat!(), .boolean(true)) XCTAssertEqual(cat1Value.getIsCat(), .boolean(true)) - + // Call with this and argument setName(this: cat1, JSValue.string("Shiro")) XCTAssertEqual(getJSValue(this: cat1, name: "name"), .string("Shiro")) @@ -384,7 +384,7 @@ class JavaScriptKitTests: XCTestCase { _ = cat1Value.setName("Chibi") XCTAssertEqual(getJSValue(this: cat1, name: "name"), .string("Chibi")) } - + func testJSObjectConversion() { let array1 = [1, 2, 3] let jsArray1 = array1.jsValue.object! @@ -392,7 +392,7 @@ class JavaScriptKitTests: XCTestCase { XCTAssertEqual(jsArray1[0], .number(1)) XCTAssertEqual(jsArray1[1], .number(2)) XCTAssertEqual(jsArray1[2], .number(3)) - + let array2: [ConvertibleToJSValue] = [1, "str", false] let jsArray2 = array2.jsValue.object! XCTAssertEqual(jsArray2.length, .number(3)) @@ -402,9 +402,9 @@ class JavaScriptKitTests: XCTestCase { _ = jsArray2.push!(5) XCTAssertEqual(jsArray2.length, .number(4)) _ = jsArray2.push!(jsArray1) - + XCTAssertEqual(jsArray2[4], .object(jsArray1)) - + let dict1: [String: JSValue] = [ "prop1": 1.jsValue, "prop2": "foo".jsValue, @@ -413,7 +413,7 @@ class JavaScriptKitTests: XCTestCase { XCTAssertEqual(jsDict1.prop1, .number(1)) XCTAssertEqual(jsDict1.prop2, .string("foo")) } - + func testObjectRefLifetime() { // ```js // global.globalObject1 = { @@ -428,24 +428,24 @@ class JavaScriptKitTests: XCTestCase { // ... // } // ``` - + let evalClosure = JSObject.global.globalObject1.eval_closure.function! let identity = JSClosure { $0[0] } let ref1 = getJSValue(this: .global, name: "globalObject1").object! let ref2 = evalClosure(identity, ref1).object! XCTAssertEqual(ref1.prop_2, .number(2)) XCTAssertEqual(ref2.prop_2, .number(2)) - -#if JAVASCRIPTKIT_WITHOUT_WEAKREFS + + #if JAVASCRIPTKIT_WITHOUT_WEAKREFS identity.release() -#endif + #endif } - + func testDate() { let date1Milliseconds = JSDate.now() let date1 = JSDate(millisecondsSinceEpoch: date1Milliseconds) let date2 = JSDate(millisecondsSinceEpoch: date1.valueOf()) - + XCTAssertEqual(date1.valueOf(), date2.valueOf()) XCTAssertEqual(date1.fullYear, date2.fullYear) XCTAssertEqual(date1.month, date2.month) @@ -464,7 +464,7 @@ class JavaScriptKitTests: XCTestCase { XCTAssertEqual(date1.utcSeconds, date2.utcSeconds) XCTAssertEqual(date1.utcMilliseconds, date2.utcMilliseconds) XCTAssertEqual(date1, date2) - + let date3 = JSDate(millisecondsSinceEpoch: 0) XCTAssertEqual(date3.valueOf(), 0) XCTAssertEqual(date3.utcFullYear, 1970) @@ -477,10 +477,10 @@ class JavaScriptKitTests: XCTestCase { XCTAssertEqual(date3.utcSeconds, 0) XCTAssertEqual(date3.utcMilliseconds, 0) XCTAssertEqual(date3.toISOString(), "1970-01-01T00:00:00.000Z") - + XCTAssertTrue(date3 < date1) } - + func testError() { let message = "test error" let expectedDescription = "Error: test error" @@ -492,21 +492,21 @@ class JavaScriptKitTests: XCTestCase { XCTAssertNil(JSError(from: .string("error"))?.description) XCTAssertEqual(JSError(from: .object(error.jsObject))?.description, expectedDescription) } - + func testJSValueAccessor() { var globalObject1 = JSObject.global.globalObject1 XCTAssertEqual(globalObject1.prop_1.nested_prop, .number(1)) XCTAssertEqual(globalObject1.object!.prop_1.object!.nested_prop, .number(1)) - + XCTAssertEqual(globalObject1.prop_4[0], .number(3)) XCTAssertEqual(globalObject1.prop_4[1], .number(4)) - + let originalProp1 = globalObject1.prop_1.object!.nested_prop globalObject1.prop_1.nested_prop = "bar" XCTAssertEqual(globalObject1.prop_1.nested_prop, .string("bar")) globalObject1.prop_1.nested_prop = originalProp1 } - + func testException() { // ```js // global.globalObject1 = { @@ -528,33 +528,33 @@ class JavaScriptKitTests: XCTestCase { // let globalObject1 = JSObject.global.globalObject1 let prop_9: JSValue = globalObject1.prop_9 - + // MARK: Throwing method calls XCTAssertThrowsError(try prop_9.object!.throwing.func1!()) { error in XCTAssertTrue(error is JSException) let errorObject = JSError(from: (error as! JSException).thrownValue) XCTAssertNotNil(errorObject) } - + XCTAssertThrowsError(try prop_9.object!.throwing.func2!()) { error in XCTAssertTrue(error is JSException) let thrownValue = (error as! JSException).thrownValue XCTAssertEqual(thrownValue.string, "String Error") } - + XCTAssertThrowsError(try prop_9.object!.throwing.func3!()) { error in XCTAssertTrue(error is JSException) let thrownValue = (error as! JSException).thrownValue XCTAssertEqual(thrownValue.number, 3.0) } - + // MARK: Simple function calls XCTAssertThrowsError(try prop_9.func1.function!.throws()) { error in XCTAssertTrue(error is JSException) let errorObject = JSError(from: (error as! JSException).thrownValue) XCTAssertNotNil(errorObject) } - + // MARK: Throwing constructor call let Animal = JSObject.global.Animal.function! XCTAssertNoThrow(try Animal.throws.new("Tama", 3, true)) @@ -564,16 +564,16 @@ class JavaScriptKitTests: XCTestCase { XCTAssertNotNil(errorObject) } } - + func testSymbols() { let symbol1 = JSSymbol("abc") let symbol2 = JSSymbol("abc") XCTAssertNotEqual(symbol1, symbol2) XCTAssertEqual(symbol1.name, symbol2.name) XCTAssertEqual(symbol1.name, "abc") - + XCTAssertEqual(JSSymbol.iterator, JSSymbol.iterator) - + // let hasInstanceClass = { // prop: function () {} // }.prop @@ -584,35 +584,36 @@ class JavaScriptKitTests: XCTestCase { let propertyDescriptor = JSObject.global.Object.function!.new() propertyDescriptor.value = JSClosure { _ in .boolean(true) }.jsValue _ = JSObject.global.Object.function!.defineProperty!( - hasInstanceClass, JSSymbol.hasInstance, + hasInstanceClass, + JSSymbol.hasInstance, propertyDescriptor ) XCTAssertEqual(hasInstanceClass[JSSymbol.hasInstance].function!().boolean, true) XCTAssertEqual(JSObject.global.Object.isInstanceOf(hasInstanceClass), true) } - + func testJSValueDecoder() { struct AnimalStruct: Decodable { let name: String let age: Int let isCat: Bool } - + let Animal = JSObject.global.Animal.function! let tama = try! Animal.throws.new("Tama", 3, true) let decoder = JSValueDecoder() let decodedTama = try! decoder.decode(AnimalStruct.self, from: tama.jsValue) - + XCTAssertEqual(decodedTama.name, tama.name.string) XCTAssertEqual(decodedTama.name, "Tama") - + XCTAssertEqual(decodedTama.age, tama.age.number.map(Int.init)) XCTAssertEqual(decodedTama.age, 3) - + XCTAssertEqual(decodedTama.isCat, tama.isCat.boolean) XCTAssertEqual(decodedTama.isCat, true) } - + func testConvertibleToJSValue() { let array1 = [1, 2, 3] let jsArray1 = array1.jsValue.object! @@ -620,7 +621,7 @@ class JavaScriptKitTests: XCTestCase { XCTAssertEqual(jsArray1[0], .number(1)) XCTAssertEqual(jsArray1[1], .number(2)) XCTAssertEqual(jsArray1[2], .number(3)) - + let array2: [ConvertibleToJSValue] = [1, "str", false] let jsArray2 = array2.jsValue.object! XCTAssertEqual(jsArray2.length, .number(3)) @@ -630,9 +631,9 @@ class JavaScriptKitTests: XCTestCase { _ = jsArray2.push!(5) XCTAssertEqual(jsArray2.length, .number(4)) _ = jsArray2.push!(jsArray1) - + XCTAssertEqual(jsArray2[4], .object(jsArray1)) - + let dict1: [String: JSValue] = [ "prop1": 1.jsValue, "prop2": "foo".jsValue, diff --git a/Tests/JavaScriptKitTests/ThreadLocalTests.swift b/Tests/JavaScriptKitTests/ThreadLocalTests.swift index 55fcdadb4..d1d736b8b 100644 --- a/Tests/JavaScriptKitTests/ThreadLocalTests.swift +++ b/Tests/JavaScriptKitTests/ThreadLocalTests.swift @@ -1,4 +1,5 @@ import XCTest + @testable import JavaScriptKit final class ThreadLocalTests: XCTestCase { diff --git a/Utilities/format.swift b/Utilities/format.swift new file mode 100755 index 000000000..206ccfc39 --- /dev/null +++ b/Utilities/format.swift @@ -0,0 +1,89 @@ +#!/usr/bin/env swift + +import class Foundation.FileManager +import class Foundation.Process +import class Foundation.ProcessInfo +import struct Foundation.URL +import func Foundation.exit + +/// The root directory of the project. +let projectRoot = URL(fileURLWithPath: #filePath).deletingLastPathComponent().deletingLastPathComponent() + +/// Returns the path to the executable if it is found in the PATH environment variable. +func which(_ executable: String) -> String? { + let pathSeparator: Character + #if os(Windows) + pathSeparator = ";" + #else + pathSeparator = ":" + #endif + let paths = ProcessInfo.processInfo.environment["PATH"]!.split(separator: pathSeparator) + for path in paths { + let url = URL(fileURLWithPath: String(path)).appendingPathComponent(executable) + if FileManager.default.isExecutableFile(atPath: url.path) { + return url.path + } + } + return nil +} + +/// Runs the `swift-format` command with the given arguments in the project root. +func swiftFormat(_ arguments: [String]) throws { + guard let swiftFormat = which("swift-format") else { + print("swift-format not found in PATH") + exit(1) + } + let task = Process() + task.executableURL = URL(fileURLWithPath: swiftFormat) + task.arguments = arguments + task.currentDirectoryURL = projectRoot + try task.run() + task.waitUntilExit() + if task.terminationStatus != 0 { + print("swift-format failed with status \(task.terminationStatus)") + exit(1) + } +} + +/// Patterns to exclude from formatting. +let excluded: Set = [ + ".git", + ".build", + ".index-build", + "node_modules", + "__Snapshots__", + // Exclude the script itself to avoid changing its file mode. + URL(fileURLWithPath: #filePath).lastPathComponent, +] + +/// Returns a list of directories to format. +func filesToFormat() -> [String] { + var files: [String] = [] + let fileManager = FileManager.default + let enumerator = fileManager.enumerator( + at: projectRoot, includingPropertiesForKeys: nil + )! + for case let fileURL as URL in enumerator { + if excluded.contains(fileURL.lastPathComponent) { + if fileURL.hasDirectoryPath { + enumerator.skipDescendants() + } + continue + } + guard fileURL.pathExtension == "swift" else { continue } + files.append(fileURL.path) + } + return files +} + +let arguments = CommandLine.arguments[1...] +switch arguments.first { +case "lint": + try swiftFormat(["lint", "--parallel", "--recursive"] + filesToFormat()) +case "format", nil: + try swiftFormat(["format", "--parallel", "--in-place", "--recursive"] + filesToFormat()) +case let subcommand?: + print("Unknown subcommand: \(subcommand)") + print("Usage: format.swift lint|format") + exit(1) +}