From bf5b1e0c29fed85713fd4a57bdd11fb39078f71f Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 5 Jun 2025 18:38:41 +0900 Subject: [PATCH 01/13] PackageToJS: Add hint for missing `.enableExperimentalFeature("Extern")` setting --- .../Sources/PackageToJSPlugin.swift | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift index e7f74e97..04f4dcd4 100644 --- a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift +++ b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift @@ -71,6 +71,27 @@ struct PackageToJSPlugin: CommandPlugin { See https://book.swiftwasm.org/getting-started/setup.html for more information. """ }), + ( + // In case the SwiftPM target using BridgeJS didn't specify `.enableExperimentalFeature("Extern")` + { build, arguments in + guard + build.logText.contains("@_extern requires '-enable-experimental-feature Extern'") + else { + return nil + } + return """ + The SwiftPM target using BridgeJS didn't specify `.enableExperimentalFeature("Extern")`. + Please add it to the target's `swiftSettings` configuration. + + For example: + ```swift + dependencies: [...], + swiftSettings: [ + .enableExperimentalFeature("Extern"), + ] + ``` + """ + }), ] private func emitHintMessage(_ message: String) { From 80821febe9462731e7f2bbd3908822b60851c07c Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 5 Jun 2025 21:05:26 +0900 Subject: [PATCH 02/13] PackageToJS: Fail tests when continuation leaks are detected --- .../SwiftTesting/Package.swift | 17 +++++++++ .../SwiftTesting/Tests/CheckTests.swift | 5 +++ .../XCTest/Package.swift | 17 +++++++++ .../XCTest/Tests/CheckTests.swift | 7 ++++ Plugins/PackageToJS/Templates/bin/test.js | 13 +++++++ Plugins/PackageToJS/Tests/ExampleTests.swift | 35 ++++++++++++++++--- 6 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/SwiftTesting/Package.swift create mode 100644 Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/SwiftTesting/Tests/CheckTests.swift create mode 100644 Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/XCTest/Package.swift create mode 100644 Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/XCTest/Tests/CheckTests.swift diff --git a/Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/SwiftTesting/Package.swift b/Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/SwiftTesting/Package.swift new file mode 100644 index 00000000..84130401 --- /dev/null +++ b/Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/SwiftTesting/Package.swift @@ -0,0 +1,17 @@ +// swift-tools-version: 6.0 +import PackageDescription + +let package = Package( + name: "Check", + dependencies: [.package(name: "JavaScriptKit", path: "../../../../../")], + targets: [ + .testTarget( + name: "CheckTests", + dependencies: [ + "JavaScriptKit", + .product(name: "JavaScriptEventLoopTestSupport", package: "JavaScriptKit"), + ], + path: "Tests" + ) + ] +) diff --git a/Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/SwiftTesting/Tests/CheckTests.swift b/Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/SwiftTesting/Tests/CheckTests.swift new file mode 100644 index 00000000..9ed73b7c --- /dev/null +++ b/Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/SwiftTesting/Tests/CheckTests.swift @@ -0,0 +1,5 @@ +import Testing + +@Test func never() async throws { + let _: Void = await withUnsafeContinuation { _ in } +} diff --git a/Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/XCTest/Package.swift b/Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/XCTest/Package.swift new file mode 100644 index 00000000..84130401 --- /dev/null +++ b/Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/XCTest/Package.swift @@ -0,0 +1,17 @@ +// swift-tools-version: 6.0 +import PackageDescription + +let package = Package( + name: "Check", + dependencies: [.package(name: "JavaScriptKit", path: "../../../../../")], + targets: [ + .testTarget( + name: "CheckTests", + dependencies: [ + "JavaScriptKit", + .product(name: "JavaScriptEventLoopTestSupport", package: "JavaScriptKit"), + ], + path: "Tests" + ) + ] +) diff --git a/Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/XCTest/Tests/CheckTests.swift b/Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/XCTest/Tests/CheckTests.swift new file mode 100644 index 00000000..324df370 --- /dev/null +++ b/Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/XCTest/Tests/CheckTests.swift @@ -0,0 +1,7 @@ +import XCTest + +final class CheckTests: XCTestCase { + func testNever() async throws { + let _: Void = await withUnsafeContinuation { _ in } + } +} diff --git a/Plugins/PackageToJS/Templates/bin/test.js b/Plugins/PackageToJS/Templates/bin/test.js index f4aad4b8..34031628 100644 --- a/Plugins/PackageToJS/Templates/bin/test.js +++ b/Plugins/PackageToJS/Templates/bin/test.js @@ -68,6 +68,19 @@ const harnesses = { options = prelude.setupOptions(options, { isMainThread: true }) } } + process.on("beforeExit", () => { + // NOTE: "beforeExit" is fired when the process exits gracefully without calling `process.exit` + // Either XCTest or swift-testing should always call `process.exit` through `proc_exit` even + // if the test succeeds. So exiting gracefully means something went wrong (e.g. withUnsafeContinuation is leaked) + // Therefore, we exit with code 1 to indicate that the test execution failed. + console.error(` + +================================================================================================= +Detected that the test execution ended without a termination signal from the testing framework. +Hint: This typically means that a continuation leak occurred. +=================================================================================================`) + process.exit(1) + }) await instantiate(options) } catch (e) { if (e instanceof WebAssembly.CompileError) { diff --git a/Plugins/PackageToJS/Tests/ExampleTests.swift b/Plugins/PackageToJS/Tests/ExampleTests.swift index ab0d1d79..9c5f260d 100644 --- a/Plugins/PackageToJS/Tests/ExampleTests.swift +++ b/Plugins/PackageToJS/Tests/ExampleTests.swift @@ -88,7 +88,6 @@ extension Trait where Self == ConditionTrait { atPath: destinationPath.path, withDestinationPath: linkDestination ) - enumerator.skipDescendants() continue } @@ -117,8 +116,11 @@ extension Trait where Self == ConditionTrait { typealias RunProcess = (_ executableURL: URL, _ args: [String], _ env: [String: String]) throws -> Void typealias RunSwift = (_ args: [String], _ env: [String: String]) throws -> Void - func withPackage(at path: String, body: (URL, _ runProcess: RunProcess, _ runSwift: RunSwift) throws -> Void) throws - { + func withPackage( + at path: String, + assertTerminationStatus: (Int32) -> Bool = { $0 == 0 }, + body: @escaping (URL, _ runProcess: RunProcess, _ runSwift: RunSwift) throws -> Void + ) throws { try withTemporaryDirectory { tempDir, retain in let destination = tempDir.appending(path: Self.repoPath.lastPathComponent) try Self.copyRepository(to: destination) @@ -139,11 +141,11 @@ extension Trait where Self == ConditionTrait { try process.run() process.waitUntilExit() - if process.terminationStatus != 0 { + if !assertTerminationStatus(process.terminationStatus) { retain = true } try #require( - process.terminationStatus == 0, + assertTerminationStatus(process.terminationStatus), """ Swift package should build successfully, check \(destination.appending(path: path).path) for details stdout: \(stdoutPath.path) @@ -275,4 +277,27 @@ extension Trait where Self == ConditionTrait { ) } } + + @Test(.requireSwiftSDK) + func continuationLeakInTest_XCTest() throws { + let swiftSDKID = try #require(Self.getSwiftSDKID()) + try withPackage( + at: "Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/XCTest", + assertTerminationStatus: { $0 != 0 } + ) { packageDir, _, runSwift in + try runSwift(["package", "--disable-sandbox", "--swift-sdk", swiftSDKID, "js", "test"], [:]) + } + } + + // TODO: Remove triple restriction once swift-testing is shipped in p1-threads SDK + @Test(.requireSwiftSDK(triple: "wasm32-unknown-wasi")) + func continuationLeakInTest_SwiftTesting() throws { + let swiftSDKID = try #require(Self.getSwiftSDKID()) + try withPackage( + at: "Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/SwiftTesting", + assertTerminationStatus: { $0 == 0 } + ) { packageDir, _, runSwift in + try runSwift(["package", "--disable-sandbox", "--swift-sdk", swiftSDKID, "js", "test"], [:]) + } + } } From f7ca331455d8985be319ddff9cbbba0bb13450bd Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 5 Jun 2025 21:22:12 +0900 Subject: [PATCH 03/13] Testing module is not included in 6.0 SDK --- Plugins/PackageToJS/Tests/ExampleTests.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Plugins/PackageToJS/Tests/ExampleTests.swift b/Plugins/PackageToJS/Tests/ExampleTests.swift index 9c5f260d..d860a685 100644 --- a/Plugins/PackageToJS/Tests/ExampleTests.swift +++ b/Plugins/PackageToJS/Tests/ExampleTests.swift @@ -289,15 +289,17 @@ extension Trait where Self == ConditionTrait { } } + #if compiler(>=6.1) // TODO: Remove triple restriction once swift-testing is shipped in p1-threads SDK @Test(.requireSwiftSDK(triple: "wasm32-unknown-wasi")) func continuationLeakInTest_SwiftTesting() throws { let swiftSDKID = try #require(Self.getSwiftSDKID()) try withPackage( at: "Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/SwiftTesting", - assertTerminationStatus: { $0 == 0 } + assertTerminationStatus: { $0 != 0 } ) { packageDir, _, runSwift in try runSwift(["package", "--disable-sandbox", "--swift-sdk", swiftSDKID, "js", "test"], [:]) } } + #endif } From a69aa7e26ae55bae5e5fb5dd04b2866d257a9c1b Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 12 Jun 2025 05:55:25 +0000 Subject: [PATCH 04/13] BridgeJS: Add runtime tests for importing TypeScript functions --- .../PackageToJS/Templates/instantiate.d.ts | 2 +- .../Generated/ImportTS.swift | 50 ++++++++++++ .../Generated/JavaScript/ImportTS.json | 77 +++++++++++++++++++ .../BridgeJSRuntimeTests/ImportAPITests.swift | 37 +++++++++ Tests/BridgeJSRuntimeTests/bridge.d.ts | 4 + Tests/prelude.mjs | 24 ++++-- 6 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 Tests/BridgeJSRuntimeTests/Generated/ImportTS.swift create mode 100644 Tests/BridgeJSRuntimeTests/Generated/JavaScript/ImportTS.json create mode 100644 Tests/BridgeJSRuntimeTests/ImportAPITests.swift create mode 100644 Tests/BridgeJSRuntimeTests/bridge.d.ts diff --git a/Plugins/PackageToJS/Templates/instantiate.d.ts b/Plugins/PackageToJS/Templates/instantiate.d.ts index 2d81ddde..2cf956e5 100644 --- a/Plugins/PackageToJS/Templates/instantiate.d.ts +++ b/Plugins/PackageToJS/Templates/instantiate.d.ts @@ -1,8 +1,8 @@ import type { /* #if USE_SHARED_MEMORY */SwiftRuntimeThreadChannel, /* #endif */SwiftRuntime } from "./runtime.js"; /* #if HAS_BRIDGE */ -// @ts-ignore export type { Imports, Exports } from "./bridge.js"; +import type { Imports, Exports } from "./bridge.js"; /* #else */ export type Imports = {} export type Exports = {} diff --git a/Tests/BridgeJSRuntimeTests/Generated/ImportTS.swift b/Tests/BridgeJSRuntimeTests/Generated/ImportTS.swift new file mode 100644 index 00000000..9ecffea5 --- /dev/null +++ b/Tests/BridgeJSRuntimeTests/Generated/ImportTS.swift @@ -0,0 +1,50 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(JSObject_id) import JavaScriptKit + +@_extern(wasm, module: "bjs", name: "make_jsstring") +private func _make_jsstring(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 + +@_extern(wasm, module: "bjs", name: "init_memory_with_result") +private func _init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) + +@_extern(wasm, module: "bjs", name: "free_jsobject") +private func _free_jsobject(_ ptr: Int32) -> Void + +func jsRoundTripVoid() -> Void { + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsRoundTripVoid") + func bjs_jsRoundTripVoid() -> Void + bjs_jsRoundTripVoid() +} + +func jsRoundTripNumber(_ v: Double) -> Double { + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsRoundTripNumber") + func bjs_jsRoundTripNumber(_ v: Float64) -> Float64 + let ret = bjs_jsRoundTripNumber(v) + return Double(ret) +} + +func jsRoundTripBool(_ v: Bool) -> Bool { + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsRoundTripBool") + func bjs_jsRoundTripBool(_ v: Int32) -> Int32 + let ret = bjs_jsRoundTripBool(Int32(v ? 1 : 0)) + return ret == 1 +} + +func jsRoundTripString(_ v: String) -> String { + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsRoundTripString") + func bjs_jsRoundTripString(_ v: Int32) -> Int32 + var v = v + let vId = v.withUTF8 { b in + _make_jsstring(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + let ret = bjs_jsRoundTripString(vId) + return String(unsafeUninitializedCapacity: Int(ret)) { b in + _init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) + return Int(ret) + } +} \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/ImportTS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/ImportTS.json new file mode 100644 index 00000000..9db7f698 --- /dev/null +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/ImportTS.json @@ -0,0 +1,77 @@ +{ + "children" : [ + { + "functions" : [ + { + "name" : "jsRoundTripVoid", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "name" : "jsRoundTripNumber", + "parameters" : [ + { + "name" : "v", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + } + }, + { + "name" : "jsRoundTripBool", + "parameters" : [ + { + "name" : "v", + "type" : { + "bool" : { + + } + } + } + ], + "returnType" : { + "bool" : { + + } + } + }, + { + "name" : "jsRoundTripString", + "parameters" : [ + { + "name" : "v", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + } + ], + "types" : [ + + ] + } + ], + "moduleName" : "BridgeJSRuntimeTests" +} \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/ImportAPITests.swift b/Tests/BridgeJSRuntimeTests/ImportAPITests.swift new file mode 100644 index 00000000..98479d20 --- /dev/null +++ b/Tests/BridgeJSRuntimeTests/ImportAPITests.swift @@ -0,0 +1,37 @@ +import XCTest +import JavaScriptKit + +class ImportAPITests: XCTestCase { + func testRoundTripVoid() { + jsRoundTripVoid() + } + + func testRoundTripNumber() { + for v in [ + 0, 1, -1, + Double(Int32.max), Double(Int32.min), + Double(Int64.max), Double(Int64.min), + Double(UInt32.max), Double(UInt32.min), + Double(UInt64.max), Double(UInt64.min), + Double.greatestFiniteMagnitude, Double.leastNonzeroMagnitude, + Double.infinity, + Double.pi, + ] { + XCTAssertEqual(jsRoundTripNumber(v), v) + } + + XCTAssert(jsRoundTripNumber(Double.nan).isNaN) + } + + func testRoundTripBool() { + for v in [true, false] { + XCTAssertEqual(jsRoundTripBool(v), v) + } + } + + func testRoundTripString() { + for v in ["", "Hello, world!", "🧑‍🧑‍🧒"] { + XCTAssertEqual(jsRoundTripString(v), v) + } + } +} diff --git a/Tests/BridgeJSRuntimeTests/bridge.d.ts b/Tests/BridgeJSRuntimeTests/bridge.d.ts new file mode 100644 index 00000000..1a092f90 --- /dev/null +++ b/Tests/BridgeJSRuntimeTests/bridge.d.ts @@ -0,0 +1,4 @@ +export function jsRoundTripVoid(): void +export function jsRoundTripNumber(v: number): number +export function jsRoundTripBool(v: boolean): boolean +export function jsRoundTripString(v: string): string diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 2501bd58..38586296 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -1,20 +1,34 @@ -/** @type {import('./../.build/plugins/PackageToJS/outputs/PackageTests/test.d.ts').Prelude["setupOptions"]} */ +/** @type {import('../.build/plugins/PackageToJS/outputs/PackageTests/test.d.ts').SetupOptions} */ export function setupOptions(options, context) { Error.stackTraceLimit = 100; setupTestGlobals(globalThis); return { ...options, + imports: { + "jsRoundTripVoid": () => { + return; + }, + "jsRoundTripNumber": (v) => { + return v; + }, + "jsRoundTripBool": (v) => { + return v; + }, + "jsRoundTripString": (v) => { + return v; + }, + }, addToCoreImports(importObject, importsContext) { const { getInstance, getExports } = importsContext; options.addToCoreImports?.(importObject, importsContext); importObject["JavaScriptEventLoopTestSupportTests"] = { "isMainThread": () => context.isMainThread, } - importObject["BridgeJSRuntimeTests"] = { - "runJsWorks": () => { - return BridgeJSRuntimeTests_runJsWorks(getInstance(), getExports()); - }, + const bridgeJSRuntimeTests = importObject["BridgeJSRuntimeTests"] || {}; + bridgeJSRuntimeTests["runJsWorks"] = () => { + return BridgeJSRuntimeTests_runJsWorks(getInstance(), getExports()); } + importObject["BridgeJSRuntimeTests"] = bridgeJSRuntimeTests; } } } From bfa4854af65005e41a963639a9606e43ed4e5121 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 12 Jun 2025 07:48:19 +0000 Subject: [PATCH 05/13] BridgeJS: Require placing `bridge-js.config.json` in target directory --- Benchmarks/Package.swift | 2 +- Benchmarks/Sources/bridge-js.config.json | 1 + .../Sources/{bridge.d.ts => bridge-js.d.ts} | 0 .../Sources/{bridge.d.ts => bridge-js.d.ts} | 0 Examples/ImportTS/Sources/main.swift | 4 +- Plugins/BridgeJS/README.md | 8 +- .../BridgeJSBuildPlugin.swift | 53 +++++++++--- .../BridgeJSCommandPlugin.swift | 86 ++++++++++++------- .../Sources/BridgeJSTool/BridgeJSTool.swift | 17 +++- .../Sources/BridgeJSTool/ExportSwift.swift | 2 +- .../BridgeJS/Sources/JavaScript/src/cli.js | 68 +++++++++------ .../Sources/JavaScript/src/processor.js | 7 +- Plugins/PackageToJS/Sources/PackageToJS.swift | 4 +- .../PackageToJS/Templates/instantiate.d.ts | 4 +- Plugins/PackageToJS/Templates/instantiate.js | 2 +- .../Articles/Ahead-of-Time-Code-Generation.md | 22 +++-- .../Importing-TypeScript-into-Swift.md | 2 +- .../bridge-js.config.json | 1 + .../{bridge.d.ts => bridge-js.d.ts} | 0 Tests/prelude.mjs | 2 +- 20 files changed, 188 insertions(+), 97 deletions(-) create mode 100644 Benchmarks/Sources/bridge-js.config.json rename Benchmarks/Sources/{bridge.d.ts => bridge-js.d.ts} (100%) rename Examples/ImportTS/Sources/{bridge.d.ts => bridge-js.d.ts} (100%) create mode 100644 Tests/BridgeJSRuntimeTests/bridge-js.config.json rename Tests/BridgeJSRuntimeTests/{bridge.d.ts => bridge-js.d.ts} (100%) diff --git a/Benchmarks/Package.swift b/Benchmarks/Package.swift index 4d59c772..8e11282e 100644 --- a/Benchmarks/Package.swift +++ b/Benchmarks/Package.swift @@ -11,7 +11,7 @@ let package = Package( .executableTarget( name: "Benchmarks", dependencies: ["JavaScriptKit"], - exclude: ["Generated/JavaScript", "bridge.d.ts"], + exclude: ["Generated/JavaScript", "bridge-js.d.ts"], swiftSettings: [ .enableExperimentalFeature("Extern") ] diff --git a/Benchmarks/Sources/bridge-js.config.json b/Benchmarks/Sources/bridge-js.config.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/Benchmarks/Sources/bridge-js.config.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Benchmarks/Sources/bridge.d.ts b/Benchmarks/Sources/bridge-js.d.ts similarity index 100% rename from Benchmarks/Sources/bridge.d.ts rename to Benchmarks/Sources/bridge-js.d.ts diff --git a/Examples/ImportTS/Sources/bridge.d.ts b/Examples/ImportTS/Sources/bridge-js.d.ts similarity index 100% rename from Examples/ImportTS/Sources/bridge.d.ts rename to Examples/ImportTS/Sources/bridge-js.d.ts diff --git a/Examples/ImportTS/Sources/main.swift b/Examples/ImportTS/Sources/main.swift index 4328b0a3..4853a966 100644 --- a/Examples/ImportTS/Sources/main.swift +++ b/Examples/ImportTS/Sources/main.swift @@ -1,9 +1,9 @@ import JavaScriptKit // This function is automatically generated by the @JS plugin -// It demonstrates how to use TypeScript functions and types imported from bridge.d.ts +// It demonstrates how to use TypeScript functions and types imported from bridge-js.d.ts @JS public func run() { - // Call the imported consoleLog function defined in bridge.d.ts + // Call the imported consoleLog function defined in bridge-js.d.ts consoleLog("Hello, World!") // Get the document object - this comes from the imported getDocument() function diff --git a/Plugins/BridgeJS/README.md b/Plugins/BridgeJS/README.md index 9cbd0401..2fb6458a 100644 --- a/Plugins/BridgeJS/README.md +++ b/Plugins/BridgeJS/README.md @@ -22,7 +22,7 @@ graph LR A.swift --> E1[[bridge-js export]] B.swift --> E1 E1 --> G1[ExportSwift.swift] - B1[bridge.d.ts]-->I1[[bridge-js import]] + B1[bridge-js.d.ts]-->I1[[bridge-js import]] I1 --> G2[ImportTS.swift] end I1 --> G4[ImportTS.json] @@ -32,7 +32,7 @@ graph LR C.swift --> E2[[bridge-js export]] D.swift --> E2 E2 --> G5[ExportSwift.swift] - B2[bridge.d.ts]-->I2[[bridge-js import]] + B2[bridge-js.d.ts]-->I2[[bridge-js import]] I2 --> G6[ImportTS.swift] end I2 --> G8[ImportTS.json] @@ -42,8 +42,8 @@ graph LR G7 --> L1 G8 --> L1 - L1 --> F1[bridge.js] - L1 --> F2[bridge.d.ts] + L1 --> F1[bridge-js.js] + L1 --> F2[bridge-js.d.ts] ModuleA -----> App[App.wasm] ModuleB -----> App diff --git a/Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift b/Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift index 4ea725ed..c9ea8987 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift @@ -11,17 +11,32 @@ struct BridgeJSBuildPlugin: BuildToolPlugin { guard let swiftSourceModuleTarget = target as? SwiftSourceModuleTarget else { return [] } - return try [ - createExportSwiftCommand(context: context, target: swiftSourceModuleTarget), - createImportTSCommand(context: context, target: swiftSourceModuleTarget), - ] + var commands: [Command] = [] + commands.append(try createExportSwiftCommand(context: context, target: swiftSourceModuleTarget)) + if let importCommand = try createImportTSCommand(context: context, target: swiftSourceModuleTarget) { + commands.append(importCommand) + } + return commands + } + + private func pathToConfigFile(target: SwiftSourceModuleTarget) -> URL { + return target.directoryURL.appending(path: "bridge-js.config.json") } private func createExportSwiftCommand(context: PluginContext, target: SwiftSourceModuleTarget) throws -> Command { let outputSwiftPath = context.pluginWorkDirectoryURL.appending(path: "ExportSwift.swift") let outputSkeletonPath = context.pluginWorkDirectoryURL.appending(path: "ExportSwift.json") - let inputFiles = target.sourceFiles.filter { !$0.url.path.hasPrefix(context.pluginWorkDirectoryURL.path + "/") } - .map(\.url) + let inputSwiftFiles = target.sourceFiles.filter { + !$0.url.path.hasPrefix(context.pluginWorkDirectoryURL.path + "/") + } + .map(\.url) + let configFile = pathToConfigFile(target: target) + let inputFiles: [URL] + if FileManager.default.fileExists(atPath: configFile.path) { + inputFiles = inputSwiftFiles + [configFile] + } else { + inputFiles = inputSwiftFiles + } return .buildCommand( displayName: "Export Swift API", executable: try context.tool(named: "BridgeJSTool").url, @@ -31,8 +46,10 @@ struct BridgeJSBuildPlugin: BuildToolPlugin { outputSkeletonPath.path, "--output-swift", outputSwiftPath.path, + // Generate the output files even if nothing is exported not to surprise + // the build system. "--always-write", "true", - ] + inputFiles.map(\.path), + ] + inputSwiftFiles.map(\.path), inputFiles: inputFiles, outputFiles: [ outputSwiftPath @@ -40,12 +57,21 @@ struct BridgeJSBuildPlugin: BuildToolPlugin { ) } - private func createImportTSCommand(context: PluginContext, target: SwiftSourceModuleTarget) throws -> Command { + private func createImportTSCommand(context: PluginContext, target: SwiftSourceModuleTarget) throws -> Command? { let outputSwiftPath = context.pluginWorkDirectoryURL.appending(path: "ImportTS.swift") let outputSkeletonPath = context.pluginWorkDirectoryURL.appending(path: "ImportTS.json") - let inputFiles = [ - target.directoryURL.appending(path: "bridge.d.ts") - ] + let inputTSFile = target.directoryURL.appending(path: "bridge-js.d.ts") + guard FileManager.default.fileExists(atPath: inputTSFile.path) else { + return nil + } + + let configFile = pathToConfigFile(target: target) + let inputFiles: [URL] + if FileManager.default.fileExists(atPath: configFile.path) { + inputFiles = [inputTSFile, configFile] + } else { + inputFiles = [inputTSFile] + } return .buildCommand( displayName: "Import TypeScript API", executable: try context.tool(named: "BridgeJSTool").url, @@ -57,10 +83,13 @@ struct BridgeJSBuildPlugin: BuildToolPlugin { outputSwiftPath.path, "--module-name", target.name, + // Generate the output files even if nothing is imported not to surprise + // the build system. "--always-write", "true", "--project", context.package.directoryURL.appending(path: "tsconfig.json").path, - ] + inputFiles.map(\.path), + inputTSFile.path, + ], inputFiles: inputFiles, outputFiles: [ outputSwiftPath diff --git a/Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift b/Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift index 286b052d..f20f7837 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift @@ -12,10 +12,12 @@ struct BridgeJSCommandPlugin: CommandPlugin { struct Options { var targets: [String] + var verbose: Bool static func parse(extractor: inout ArgumentExtractor) -> Options { let targets = extractor.extractOption(named: "target") - return Options(targets: targets) + let verbose = extractor.extractFlag(named: "verbose") + return Options(targets: targets, verbose: verbose != 0) } static func help() -> String { @@ -29,13 +31,13 @@ struct BridgeJSCommandPlugin: CommandPlugin { OPTIONS: --target Specify target(s) to generate bridge code for. If omitted, generates for all targets with JavaScriptKit dependency. + --verbose Print verbose output. """ } } func performCommand(context: PluginContext, arguments: [String]) throws { // Check for help flags to display usage information - // This allows users to run `swift package plugin bridge-js --help` to understand the plugin's functionality if arguments.contains(where: { ["-h", "--help"].contains($0) }) { printStderr(Options.help()) return @@ -45,25 +47,31 @@ struct BridgeJSCommandPlugin: CommandPlugin { let options = Options.parse(extractor: &extractor) let remainingArguments = extractor.remainingArguments + let context = Context(options: options, context: context) + if options.targets.isEmpty { - try runOnTargets( - context: context, + try context.runOnTargets( remainingArguments: remainingArguments, where: { target in target.hasDependency(named: Self.JAVASCRIPTKIT_PACKAGE_NAME) } ) } else { - try runOnTargets( - context: context, + try context.runOnTargets( remainingArguments: remainingArguments, where: { options.targets.contains($0.name) } ) } } - private func runOnTargets( - context: PluginContext, + struct Context { + let options: Options + let context: PluginContext + } +} + +extension BridgeJSCommandPlugin.Context { + func runOnTargets( remainingArguments: [String], where predicate: (SwiftSourceModuleTarget) -> Bool ) throws { @@ -71,57 +79,71 @@ struct BridgeJSCommandPlugin: CommandPlugin { guard let target = target as? SwiftSourceModuleTarget else { continue } + let configFilePath = target.directoryURL.appending(path: "bridge-js.config.json") + if !FileManager.default.fileExists(atPath: configFilePath.path) { + printVerbose("No bridge-js.config.json found for \(target.name), skipping...") + continue + } guard predicate(target) else { continue } - try runSingleTarget(context: context, target: target, remainingArguments: remainingArguments) + try runSingleTarget(target: target, remainingArguments: remainingArguments) } } private func runSingleTarget( - context: PluginContext, target: SwiftSourceModuleTarget, remainingArguments: [String] ) throws { - Diagnostics.progress("Exporting Swift API for \(target.name)...") + printStderr("Generating bridge code for \(target.name)...") + + printVerbose("Exporting Swift API for \(target.name)...") let generatedDirectory = target.directoryURL.appending(path: "Generated") let generatedJavaScriptDirectory = generatedDirectory.appending(path: "JavaScript") try runBridgeJSTool( - context: context, arguments: [ "export", "--output-skeleton", generatedJavaScriptDirectory.appending(path: "ExportSwift.json").path, "--output-swift", generatedDirectory.appending(path: "ExportSwift.swift").path, + "--verbose", + options.verbose ? "true" : "false", ] + target.sourceFiles.filter { !$0.url.path.hasPrefix(generatedDirectory.path + "/") }.map(\.url.path) + remainingArguments ) - try runBridgeJSTool( - context: context, - arguments: [ - "import", - "--output-skeleton", - generatedJavaScriptDirectory.appending(path: "ImportTS.json").path, - "--output-swift", - generatedDirectory.appending(path: "ImportTS.swift").path, - "--module-name", - target.name, - "--project", - context.package.directoryURL.appending(path: "tsconfig.json").path, - target.directoryURL.appending(path: "bridge.d.ts").path, - ] + remainingArguments - ) + printVerbose("Importing TypeScript API for \(target.name)...") + + let bridgeDtsPath = target.directoryURL.appending(path: "bridge-js.d.ts") + // Execute import only if bridge-js.d.ts exists + if FileManager.default.fileExists(atPath: bridgeDtsPath.path) { + try runBridgeJSTool( + arguments: [ + "import", + "--output-skeleton", + generatedJavaScriptDirectory.appending(path: "ImportTS.json").path, + "--output-swift", + generatedDirectory.appending(path: "ImportTS.swift").path, + "--verbose", + options.verbose ? "true" : "false", + "--module-name", + target.name, + "--project", + context.package.directoryURL.appending(path: "tsconfig.json").path, + bridgeDtsPath.path, + ] + remainingArguments + ) + } } - private func runBridgeJSTool(context: PluginContext, arguments: [String]) throws { + private func runBridgeJSTool(arguments: [String]) throws { let tool = try context.tool(named: "BridgeJSTool").url - printStderr("$ \(tool.path) \(arguments.joined(separator: " "))") + printVerbose("$ \(tool.path) \(arguments.joined(separator: " "))") let process = Process() process.executableURL = tool process.arguments = arguments @@ -133,6 +155,12 @@ struct BridgeJSCommandPlugin: CommandPlugin { exit(process.terminationStatus) } } + + private func printVerbose(_ message: String) { + if options.verbose { + printStderr(message) + } + } } private func printStderr(_ message: String) { diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift index a6bd5ff5..396adcc2 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift @@ -57,7 +57,6 @@ import SwiftParser """ ) } - let progress = ProgressReporting() switch subcommand { case "import": let parser = ArgumentParser( @@ -71,6 +70,10 @@ import SwiftParser help: "Always write the output files even if no APIs are imported", required: false ), + "verbose": OptionRule( + help: "Print verbose output", + required: false + ), "output-swift": OptionRule(help: "The output file path for the Swift source code", required: true), "output-skeleton": OptionRule( help: "The output file path for the skeleton of the imported TypeScript APIs", @@ -85,6 +88,7 @@ import SwiftParser let (positionalArguments, _, doubleDashOptions) = try parser.parse( arguments: Array(arguments.dropFirst()) ) + let progress = ProgressReporting(verbose: doubleDashOptions["verbose"] == "true") var importer = ImportTS(progress: progress, moduleName: doubleDashOptions["module-name"]!) for inputFile in positionalArguments { if inputFile.hasSuffix(".json") { @@ -145,11 +149,16 @@ import SwiftParser help: "Always write the output files even if no APIs are exported", required: false ), + "verbose": OptionRule( + help: "Print verbose output", + required: false + ), ] ) let (positionalArguments, _, doubleDashOptions) = try parser.parse( arguments: Array(arguments.dropFirst()) ) + let progress = ProgressReporting(verbose: doubleDashOptions["verbose"] == "true") let exporter = ExportSwift(progress: progress) for inputFile in positionalArguments { let sourceURL = URL(fileURLWithPath: inputFile) @@ -253,7 +262,11 @@ private func printStderr(_ message: String) { struct ProgressReporting { let print: (String) -> Void - init(print: @escaping (String) -> Void = { Swift.print($0) }) { + init(verbose: Bool) { + self.init(print: verbose ? { Swift.print($0) } : { _ in }) + } + + private init(print: @escaping (String) -> Void) { self.print = print } diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSTool/ExportSwift.swift index 9b401347..2e0180fa 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSTool/ExportSwift.swift @@ -19,7 +19,7 @@ class ExportSwift { private var exportedClasses: [ExportedClass] = [] private var typeDeclResolver: TypeDeclResolver = TypeDeclResolver() - init(progress: ProgressReporting = ProgressReporting()) { + init(progress: ProgressReporting) { self.progress = progress } diff --git a/Plugins/BridgeJS/Sources/JavaScript/src/cli.js b/Plugins/BridgeJS/Sources/JavaScript/src/cli.js index 6d2a1ed8..f708082c 100644 --- a/Plugins/BridgeJS/Sources/JavaScript/src/cli.js +++ b/Plugins/BridgeJS/Sources/JavaScript/src/cli.js @@ -6,7 +6,15 @@ import ts from 'typescript'; import path from 'path'; class DiagnosticEngine { - constructor() { + /** + * @param {string} level + */ + constructor(level) { + const levelInfo = DiagnosticEngine.LEVELS[level]; + if (!levelInfo) { + throw new Error(`Invalid log level: ${level}`); + } + this.minLevel = levelInfo.level; /** @type {ts.FormatDiagnosticsHost} */ this.formattHost = { getCanonicalFileName: (fileName) => fileName, @@ -23,36 +31,36 @@ class DiagnosticEngine { console.log(message); } - /** - * @param {string} message - * @param {ts.Node | undefined} node - */ - info(message, node = undefined) { - this.printLog("info", '\x1b[32m', message, node); - } - - /** - * @param {string} message - * @param {ts.Node | undefined} node - */ - warn(message, node = undefined) { - this.printLog("warning", '\x1b[33m', message, node); - } - - /** - * @param {string} message - */ - error(message) { - this.printLog("error", '\x1b[31m', message); + static LEVELS = { + "verbose": { + color: '\x1b[34m', + level: 0, + }, + "info": { + color: '\x1b[32m', + level: 1, + }, + "warning": { + color: '\x1b[33m', + level: 2, + }, + "error": { + color: '\x1b[31m', + level: 3, + }, } /** - * @param {string} level - * @param {string} color + * @param {keyof typeof DiagnosticEngine.LEVELS} level * @param {string} message * @param {ts.Node | undefined} node */ - printLog(level, color, message, node = undefined) { + print(level, message, node = undefined) { + const levelInfo = DiagnosticEngine.LEVELS[level]; + if (levelInfo.level < this.minLevel) { + return; + } + const color = levelInfo.color; if (node) { const sourceFile = node.getSourceFile(); const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); @@ -85,7 +93,11 @@ export function main(args) { project: { type: 'string', short: 'p', - } + }, + "log-level": { + type: 'string', + default: 'info', + }, }, allowPositionals: true }) @@ -102,9 +114,9 @@ export function main(args) { } const filePath = options.positionals[0]; - const diagnosticEngine = new DiagnosticEngine(); + const diagnosticEngine = new DiagnosticEngine(options.values["log-level"] || "info"); - diagnosticEngine.info(`Processing ${filePath}...`); + diagnosticEngine.print("verbose", `Processing ${filePath}...`); // Create TypeScript program and process declarations const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile); diff --git a/Plugins/BridgeJS/Sources/JavaScript/src/processor.js b/Plugins/BridgeJS/Sources/JavaScript/src/processor.js index e3887b3c..d4c72d28 100644 --- a/Plugins/BridgeJS/Sources/JavaScript/src/processor.js +++ b/Plugins/BridgeJS/Sources/JavaScript/src/processor.js @@ -16,8 +16,7 @@ import ts from 'typescript'; /** * @typedef {{ - * warn: (message: string, node?: ts.Node) => void, - * error: (message: string, node?: ts.Node) => void, + * print: (level: "warning" | "error", message: string, node?: ts.Node) => void, * }} DiagnosticEngine */ @@ -97,7 +96,7 @@ export class TypeProcessor { } }); } catch (error) { - this.diagnosticEngine.error(`Error processing ${sourceFile.fileName}: ${error.message}`); + this.diagnosticEngine.print("error", `Error processing ${sourceFile.fileName}: ${error.message}`); } } @@ -383,7 +382,7 @@ export class TypeProcessor { const typeName = this.deriveTypeName(type); if (!typeName) { - this.diagnosticEngine.warn(`Unknown non-nominal type: ${typeString}`, node); + this.diagnosticEngine.print("warning", `Unknown non-nominal type: ${typeString}`, node); return { "jsObject": {} }; } this.seenTypes.set(type, node); diff --git a/Plugins/PackageToJS/Sources/PackageToJS.swift b/Plugins/PackageToJS/Sources/PackageToJS.swift index 2b8b4458..43e2c244 100644 --- a/Plugins/PackageToJS/Sources/PackageToJS.swift +++ b/Plugins/PackageToJS/Sources/PackageToJS.swift @@ -569,8 +569,8 @@ struct PackagingPlanner { "BridgeJS is still an experimental feature. Set the environment variable JAVASCRIPTKIT_EXPERIMENTAL_BRIDGEJS=1 to enable." ) } - let bridgeJs = outputDir.appending(path: "bridge.js") - let bridgeDts = outputDir.appending(path: "bridge.d.ts") + let bridgeJs = outputDir.appending(path: "bridge-js.js") + let bridgeDts = outputDir.appending(path: "bridge-js.d.ts") packageInputs.append( make.addTask(inputFiles: exportedSkeletons + importedSkeletons, output: bridgeJs) { _, scope in let link = try BridgeJSLink( diff --git a/Plugins/PackageToJS/Templates/instantiate.d.ts b/Plugins/PackageToJS/Templates/instantiate.d.ts index 2cf956e5..e42e4f2f 100644 --- a/Plugins/PackageToJS/Templates/instantiate.d.ts +++ b/Plugins/PackageToJS/Templates/instantiate.d.ts @@ -1,8 +1,8 @@ import type { /* #if USE_SHARED_MEMORY */SwiftRuntimeThreadChannel, /* #endif */SwiftRuntime } from "./runtime.js"; /* #if HAS_BRIDGE */ -export type { Imports, Exports } from "./bridge.js"; -import type { Imports, Exports } from "./bridge.js"; +export type { Imports, Exports } from "./bridge-js.js"; +import type { Imports, Exports } from "./bridge-js.js"; /* #else */ export type Imports = {} export type Exports = {} diff --git a/Plugins/PackageToJS/Templates/instantiate.js b/Plugins/PackageToJS/Templates/instantiate.js index 4a3a3222..65996d86 100644 --- a/Plugins/PackageToJS/Templates/instantiate.js +++ b/Plugins/PackageToJS/Templates/instantiate.js @@ -15,7 +15,7 @@ export const MEMORY_TYPE = { /* #if HAS_BRIDGE */ // @ts-ignore -import { createInstantiator } from "./bridge.js" +import { createInstantiator } from "./bridge-js.js" /* #else */ /** * @param {import('./instantiate.d').InstantiateOptions} options diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/Ahead-of-Time-Code-Generation.md b/Sources/JavaScriptKit/Documentation.docc/Articles/Ahead-of-Time-Code-Generation.md index 755f68b9..e3f52885 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/Ahead-of-Time-Code-Generation.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/Ahead-of-Time-Code-Generation.md @@ -44,7 +44,15 @@ let package = Package( ) ``` -### Step 2: Create Your Swift Code with @JS Annotations +### Step 2: Create BridgeJS Configuration + +Create a `bridge-js.config.json` file in your SwiftPM target directory you want to use BridgeJS. + +```console +$ echo "{}" > Sources/MyApp/bridge-js.config.json +``` + +### Step 3: Create Your Swift Code with @JS Annotations Write your Swift code with `@JS` annotations as usual: @@ -70,12 +78,12 @@ import JavaScriptKit } ``` -### Step 3: Create Your TypeScript Definitions +### Step 4: Create Your TypeScript Definitions -If you're importing JavaScript APIs, create your `bridge.d.ts` file as usual: +If you're importing JavaScript APIs, create your `bridge-js.d.ts` file as usual: ```typescript -// Sources/MyApp/bridge.d.ts +// Sources/MyApp/bridge-js.d.ts export function consoleLog(message: string): void; export interface Document { @@ -86,7 +94,7 @@ export interface Document { export function getDocument(): Document; ``` -### Step 4: Generate the Bridge Code +### Step 5: Generate the Bridge Code Run the command plugin to generate the bridge code: @@ -108,7 +116,7 @@ Sources/MyApp/Generated/ImportTS.swift # Generated code for TypeScript impor Sources/MyApp/Generated/JavaScript/ # Generated JSON skeletons ``` -### Step 5: Add Generated Files to Version Control +### Step 6: Add Generated Files to Version Control Add these generated files to your version control system: @@ -117,7 +125,7 @@ git add Sources/MyApp/Generated git commit -m "Add generated BridgeJS code" ``` -### Step 6: Build Your Package +### Step 7: Build Your Package Now you can build your package as usual: diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/Importing-TypeScript-into-Swift.md b/Sources/JavaScriptKit/Documentation.docc/Articles/Importing-TypeScript-into-Swift.md index 5f9bb4a1..98a9c80c 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/Importing-TypeScript-into-Swift.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/Importing-TypeScript-into-Swift.md @@ -51,7 +51,7 @@ let package = Package( ### Step 2: Create TypeScript Definitions -Create a file named `bridge.d.ts` in your target source directory (e.g. `Sources//bridge.d.ts`). This file defines the JavaScript APIs you want to use in Swift: +Create a file named `bridge-js.d.ts` in your target source directory (e.g. `Sources//bridge-js.d.ts`). This file defines the JavaScript APIs you want to use in Swift: ```typescript // Simple function diff --git a/Tests/BridgeJSRuntimeTests/bridge-js.config.json b/Tests/BridgeJSRuntimeTests/bridge-js.config.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/Tests/BridgeJSRuntimeTests/bridge-js.config.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/bridge.d.ts b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts similarity index 100% rename from Tests/BridgeJSRuntimeTests/bridge.d.ts rename to Tests/BridgeJSRuntimeTests/bridge-js.d.ts diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 38586296..a1af2a76 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -35,7 +35,7 @@ export function setupOptions(options, context) { import assert from "node:assert"; -/** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge.d.ts').Exports} exports */ +/** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { exports.roundTripVoid(); for (const v of [0, 1, -1, 2147483647, -2147483648]) { From 328a5b7b5c59fb3190bcd0945a23e291b8aa286a Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 13 Jun 2025 05:37:15 +0000 Subject: [PATCH 06/13] BridgeJS: Factor out import object builder --- .../Sources/BridgeJSLink/BridgeJSLink.swift | 131 +++++++++++------- 1 file changed, 78 insertions(+), 53 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index e62a9a63..d6db7e77 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -47,10 +47,8 @@ struct BridgeJSLink { func link() throws -> (outputJs: String, outputDts: String) { var exportsLines: [String] = [] - var importedLines: [String] = [] var classLines: [String] = [] var dtsExportLines: [String] = [] - var dtsImportLines: [String] = [] var dtsClassLines: [String] = [] if exportedSkeletons.contains(where: { $0.classes.count > 0 }) { @@ -84,57 +82,18 @@ struct BridgeJSLink { } } + var importObjectBuilders: [ImportObjectBuilder] = [] for skeletonSet in importedSkeletons { - importedLines.append("const \(skeletonSet.moduleName) = importObject[\"\(skeletonSet.moduleName)\"] = {};") - func assignToImportObject(name: String, function: [String]) { - var js = function - js[0] = "\(skeletonSet.moduleName)[\"\(name)\"] = " + js[0] - importedLines.append(contentsOf: js) - } + let importObjectBuilder = ImportObjectBuilder(moduleName: skeletonSet.moduleName) for fileSkeleton in skeletonSet.children { for function in fileSkeleton.functions { - let (js, dts) = try renderImportedFunction(function: function) - assignToImportObject(name: function.abiName(context: nil), function: js) - dtsImportLines.append(contentsOf: dts) + try renderImportedFunction(importObjectBuilder: importObjectBuilder, function: function) } for type in fileSkeleton.types { - for property in type.properties { - let getterAbiName = property.getterAbiName(context: type) - let (js, dts) = try renderImportedProperty( - property: property, - abiName: getterAbiName, - emitCall: { thunkBuilder in - thunkBuilder.callPropertyGetter(name: property.name, returnType: property.type) - return try thunkBuilder.lowerReturnValue(returnType: property.type) - } - ) - assignToImportObject(name: getterAbiName, function: js) - dtsImportLines.append(contentsOf: dts) - - if !property.isReadonly { - let setterAbiName = property.setterAbiName(context: type) - let (js, dts) = try renderImportedProperty( - property: property, - abiName: setterAbiName, - emitCall: { thunkBuilder in - thunkBuilder.liftParameter( - param: Parameter(label: nil, name: "newValue", type: property.type) - ) - thunkBuilder.callPropertySetter(name: property.name, returnType: property.type) - return nil - } - ) - assignToImportObject(name: setterAbiName, function: js) - dtsImportLines.append(contentsOf: dts) - } - } - for method in type.methods { - let (js, dts) = try renderImportedMethod(context: type, method: method) - assignToImportObject(name: method.abiName(context: type), function: js) - dtsImportLines.append(contentsOf: dts) - } + try renderImportedType(importObjectBuilder: importObjectBuilder, type: type) } } + importObjectBuilders.append(importObjectBuilder) } let outputJs = """ @@ -175,7 +134,7 @@ struct BridgeJSLink { target.set(tmpRetBytes); tmpRetBytes = undefined; } - \(importedLines.map { $0.indent(count: 12) }.joined(separator: "\n")) + \(importObjectBuilders.flatMap { $0.importedLines }.map { $0.indent(count: 12) }.joined(separator: "\n")) }, setInstance: (i) => { instance = i; @@ -198,7 +157,7 @@ struct BridgeJSLink { dtsLines.append(contentsOf: dtsExportLines.map { $0.indent(count: 4) }) dtsLines.append("}") dtsLines.append("export type Imports = {") - dtsLines.append(contentsOf: dtsImportLines.map { $0.indent(count: 4) }) + dtsLines.append(contentsOf: importObjectBuilders.flatMap { $0.dtsImportLines }.map { $0.indent(count: 4) }) dtsLines.append("}") let outputDts = """ // NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, @@ -475,7 +434,31 @@ struct BridgeJSLink { } } - func renderImportedFunction(function: ImportedFunctionSkeleton) throws -> (js: [String], dts: [String]) { + class ImportObjectBuilder { + var moduleName: String + var importedLines: [String] = [] + var dtsImportLines: [String] = [] + + init(moduleName: String) { + self.moduleName = moduleName + importedLines.append("const \(moduleName) = importObject[\"\(moduleName)\"] = {};") + } + + func assignToImportObject(name: String, function: [String]) { + var js = function + js[0] = "\(moduleName)[\"\(name)\"] = " + js[0] + importedLines.append(contentsOf: js) + } + + func appendDts(_ lines: [String]) { + dtsImportLines.append(contentsOf: lines) + } + } + + func renderImportedFunction( + importObjectBuilder: ImportObjectBuilder, + function: ImportedFunctionSkeleton + ) throws { let thunkBuilder = ImportedThunkBuilder() for param in function.parameters { thunkBuilder.liftParameter(param: param) @@ -486,11 +469,53 @@ struct BridgeJSLink { name: function.abiName(context: nil), returnExpr: returnExpr ) - var dtsLines: [String] = [] - dtsLines.append( - "\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType));" + importObjectBuilder.appendDts( + [ + "\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType));" + ] ) - return (funcLines, dtsLines) + importObjectBuilder.assignToImportObject(name: function.abiName(context: nil), function: funcLines) + } + + func renderImportedType( + importObjectBuilder: ImportObjectBuilder, + type: ImportedTypeSkeleton + ) throws { + for property in type.properties { + let getterAbiName = property.getterAbiName(context: type) + let (js, dts) = try renderImportedProperty( + property: property, + abiName: getterAbiName, + emitCall: { thunkBuilder in + thunkBuilder.callPropertyGetter(name: property.name, returnType: property.type) + return try thunkBuilder.lowerReturnValue(returnType: property.type) + } + ) + importObjectBuilder.assignToImportObject(name: getterAbiName, function: js) + importObjectBuilder.appendDts(dts) + + if !property.isReadonly { + let setterAbiName = property.setterAbiName(context: type) + let (js, dts) = try renderImportedProperty( + property: property, + abiName: setterAbiName, + emitCall: { thunkBuilder in + thunkBuilder.liftParameter( + param: Parameter(label: nil, name: "newValue", type: property.type) + ) + thunkBuilder.callPropertySetter(name: property.name, returnType: property.type) + return nil + } + ) + importObjectBuilder.assignToImportObject(name: setterAbiName, function: js) + importObjectBuilder.appendDts(dts) + } + } + for method in type.methods { + let (js, dts) = try renderImportedMethod(context: type, method: method) + importObjectBuilder.assignToImportObject(name: method.abiName(context: type), function: js) + importObjectBuilder.appendDts(dts) + } } func renderImportedProperty( From 3b305b797883ae83f6e5738d0a59998afef1b025 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 13 Jun 2025 05:38:08 +0000 Subject: [PATCH 07/13] BridgeJS: Fix JSObject assignment in `init` for imported TS class --- Plugins/BridgeJS/Sources/BridgeJSTool/ImportTS.swift | 2 +- .../__Snapshots__/ImportTSTests/TypeScriptClass.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSTool/ImportTS.swift index a97550bd..bf269a95 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSTool/ImportTS.swift @@ -237,7 +237,7 @@ struct ImportTS { preconditionFailure("assignThis can only be called with a jsObject return type") } abiReturnType = .i32 - body.append("self.this = ret") + body.append("self.this = JSObject(id: UInt32(bitPattern: ret))") } func renderImportDecl() -> DeclSyntax { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift index 993a1417..0f1f42d1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift @@ -34,7 +34,7 @@ struct Greeter { _make_jsstring(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) } let ret = bjs_Greeter_init(nameId) - self.this = ret + self.this = JSObject(id: UInt32(bitPattern: ret)) } func greet() -> String { From 86a532e69eab081072d33c8cd63dfd6354827673 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 13 Jun 2025 05:55:09 +0000 Subject: [PATCH 08/13] BridgeJS: Add helper `SetupOptionsFn` type to test.d.ts --- Plugins/PackageToJS/Templates/test.d.ts | 7 +++++++ Plugins/PackageToJS/Templates/test.js | 1 + Tests/prelude.mjs | 6 ++++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Plugins/PackageToJS/Templates/test.d.ts b/Plugins/PackageToJS/Templates/test.d.ts index 2968f6dd..21383997 100644 --- a/Plugins/PackageToJS/Templates/test.d.ts +++ b/Plugins/PackageToJS/Templates/test.d.ts @@ -1,5 +1,12 @@ import type { InstantiateOptions, instantiate } from "./instantiate"; +export type SetupOptionsFn = ( + options: InstantiateOptions, + context: { + isMainThread: boolean, + } +) => Promise + export function testBrowser( options: { preludeScript?: string, diff --git a/Plugins/PackageToJS/Templates/test.js b/Plugins/PackageToJS/Templates/test.js index b44b0d6e..518dacf2 100644 --- a/Plugins/PackageToJS/Templates/test.js +++ b/Plugins/PackageToJS/Templates/test.js @@ -157,6 +157,7 @@ export async function testBrowserInPage(options, processInfo) { }); const { instantiate } = await import("./instantiate.js"); + /** @type {import('./test.d.ts').SetupOptionsFn} */ let setupOptions = (options, _) => { return options }; if (processInfo.preludeScript) { const prelude = await import(processInfo.preludeScript); diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index a1af2a76..5de936e1 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -1,5 +1,7 @@ -/** @type {import('../.build/plugins/PackageToJS/outputs/PackageTests/test.d.ts').SetupOptions} */ -export function setupOptions(options, context) { +// @ts-check + +/** @type {import('../.build/plugins/PackageToJS/outputs/PackageTests/test.d.ts').SetupOptionsFn} */ +export async function setupOptions(options, context) { Error.stackTraceLimit = 100; setupTestGlobals(globalThis); return { From 304ee67c80ff60c868da69d85eefd5d34117c916 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 13 Jun 2025 06:10:32 +0000 Subject: [PATCH 09/13] BridgeJS: Add support for imported TypeScript constructors --- .../Sources/BridgeJSLink/BridgeJSLink.swift | 41 ++++++++++++++++++- .../BridgeJSLinkTests/Interface.Import.d.ts | 2 +- .../TypeScriptClass.Import.d.ts | 3 ++ .../TypeScriptClass.Import.js | 6 +++ 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index d6db7e77..f44cf2e3 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -396,6 +396,11 @@ struct BridgeJSLink { } } + func callConstructor(name: String) { + let call = "new options.imports.\(name)(\(parameterForwardings.joined(separator: ", ")))" + bodyLines.append("let ret = \(call);") + } + func callMethod(name: String, returnType: BridgeType) { let call = "swift.memory.getObject(self).\(name)(\(parameterForwardings.joined(separator: ", ")))" if returnType == .void { @@ -481,6 +486,13 @@ struct BridgeJSLink { importObjectBuilder: ImportObjectBuilder, type: ImportedTypeSkeleton ) throws { + if let constructor = type.constructor { + try renderImportedConstructor( + importObjectBuilder: importObjectBuilder, + type: type, + constructor: constructor + ) + } for property in type.properties { let getterAbiName = property.getterAbiName(context: type) let (js, dts) = try renderImportedProperty( @@ -518,6 +530,31 @@ struct BridgeJSLink { } } + func renderImportedConstructor( + importObjectBuilder: ImportObjectBuilder, + type: ImportedTypeSkeleton, + constructor: ImportedConstructorSkeleton + ) throws { + let thunkBuilder = ImportedThunkBuilder() + for param in constructor.parameters { + thunkBuilder.liftParameter(param: param) + } + let returnType = BridgeType.jsObject(type.name) + thunkBuilder.callConstructor(name: type.name) + let returnExpr = try thunkBuilder.lowerReturnValue(returnType: returnType) + let abiName = constructor.abiName(context: type) + let funcLines = thunkBuilder.renderFunction( + name: abiName, + returnExpr: returnExpr + ) + importObjectBuilder.assignToImportObject(name: abiName, function: funcLines) + importObjectBuilder.appendDts([ + "\(type.name): {", + "new\(renderTSSignature(parameters: constructor.parameters, returnType: returnType));".indent(count: 4), + "}" + ]) + } + func renderImportedProperty( property: ImportedPropertySkeleton, abiName: String, @@ -577,8 +614,8 @@ extension BridgeType { return "number" case .bool: return "boolean" - case .jsObject: - return "any" + case .jsObject(let name): + return name ?? "any" case .swiftHeapObject(let name): return name } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.d.ts index 1e7ca6ab..ffcbcd14 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.d.ts @@ -7,7 +7,7 @@ export type Exports = { } export type Imports = { - returnAnimatable(): any; + returnAnimatable(): Animatable; } export function createInstantiator(options: { imports: Imports; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.d.ts index 818d57a9..bcbcf06f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.d.ts @@ -7,6 +7,9 @@ export type Exports = { } export type Imports = { + Greeter: { + new(name: string): Greeter; + } } export function createInstantiator(options: { imports: Imports; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js index c7ae6a22..2111af96 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js @@ -36,6 +36,12 @@ export async function createInstantiator(options, swift) { tmpRetBytes = undefined; } const TestModule = importObject["TestModule"] = {}; + TestModule["bjs_Greeter_init"] = function bjs_Greeter_init(name) { + const nameObject = swift.memory.getObject(name); + swift.memory.release(name); + let ret = new options.imports.Greeter(nameObject); + return swift.memory.retain(ret); + } TestModule["bjs_Greeter_greet"] = function bjs_Greeter_greet(self) { let ret = swift.memory.getObject(self).greet(); tmpRetBytes = textEncoder.encode(ret); From b52151cb78520906a46d0f887c71eb79ba255381 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 13 Jun 2025 06:19:06 +0000 Subject: [PATCH 10/13] BridgeJS: Add runtime tests for importing TypeScript classes --- Plugins/PackageToJS/Templates/bin/test.js | 2 +- .../PackageToJS/Templates/platforms/node.js | 2 +- .../Generated/ImportTS.swift | 44 +++++++++++++++++ .../Generated/JavaScript/ImportTS.json | 48 +++++++++++++++++++ .../BridgeJSRuntimeTests/ImportAPITests.swift | 7 +++ Tests/BridgeJSRuntimeTests/bridge-js.d.ts | 6 +++ Tests/prelude.mjs | 13 +++++ 7 files changed, 120 insertions(+), 2 deletions(-) diff --git a/Plugins/PackageToJS/Templates/bin/test.js b/Plugins/PackageToJS/Templates/bin/test.js index 34031628..e7444e90 100644 --- a/Plugins/PackageToJS/Templates/bin/test.js +++ b/Plugins/PackageToJS/Templates/bin/test.js @@ -65,7 +65,7 @@ const harnesses = { if (preludeScript) { const prelude = await import(preludeScript) if (prelude.setupOptions) { - options = prelude.setupOptions(options, { isMainThread: true }) + options = await prelude.setupOptions(options, { isMainThread: true }) } } process.on("beforeExit", () => { diff --git a/Plugins/PackageToJS/Templates/platforms/node.js b/Plugins/PackageToJS/Templates/platforms/node.js index c45bdf35..aff708be 100644 --- a/Plugins/PackageToJS/Templates/platforms/node.js +++ b/Plugins/PackageToJS/Templates/platforms/node.js @@ -59,7 +59,7 @@ export function createDefaultWorkerFactory(preludeScript) { if (preludeScript) { const prelude = await import(preludeScript); if (prelude.setupOptions) { - options = prelude.setupOptions(options, { isMainThread: false }) + options = await prelude.setupOptions(options, { isMainThread: false }) } } await instantiateForThread(tid, startArg, { diff --git a/Tests/BridgeJSRuntimeTests/Generated/ImportTS.swift b/Tests/BridgeJSRuntimeTests/Generated/ImportTS.swift index 9ecffea5..f479a071 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/ImportTS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/ImportTS.swift @@ -47,4 +47,48 @@ func jsRoundTripString(_ v: String) -> String { _init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) return Int(ret) } +} + +struct JsGreeter { + let this: JSObject + + init(this: JSObject) { + self.this = this + } + + init(takingThis this: Int32) { + self.this = JSObject(id: UInt32(bitPattern: this)) + } + + init(_ name: String) { + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_init") + func bjs_JsGreeter_init(_ name: Int32) -> Int32 + var name = name + let nameId = name.withUTF8 { b in + _make_jsstring(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + let ret = bjs_JsGreeter_init(nameId) + self.this = JSObject(id: UInt32(bitPattern: ret)) + } + + func greet() -> String { + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_greet") + func bjs_JsGreeter_greet(_ self: Int32) -> Int32 + let ret = bjs_JsGreeter_greet(Int32(bitPattern: self.this.id)) + return String(unsafeUninitializedCapacity: Int(ret)) { b in + _init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) + return Int(ret) + } + } + + func changeName(_ name: String) -> Void { + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_changeName") + func bjs_JsGreeter_changeName(_ self: Int32, _ name: Int32) -> Void + var name = name + let nameId = name.withUTF8 { b in + _make_jsstring(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + bjs_JsGreeter_changeName(Int32(bitPattern: self.this.id), nameId) + } + } \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/ImportTS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/ImportTS.json index 9db7f698..867957d9 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/ImportTS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/ImportTS.json @@ -69,7 +69,55 @@ } ], "types" : [ + { + "constructor" : { + "parameters" : [ + { + "name" : "name", + "type" : { + "string" : { + + } + } + } + ] + }, + "methods" : [ + { + "name" : "greet", + "parameters" : [ + ], + "returnType" : { + "string" : { + + } + } + }, + { + "name" : "changeName", + "parameters" : [ + { + "name" : "name", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "void" : { + + } + } + } + ], + "name" : "JsGreeter", + "properties" : [ + + ] + } ] } ], diff --git a/Tests/BridgeJSRuntimeTests/ImportAPITests.swift b/Tests/BridgeJSRuntimeTests/ImportAPITests.swift index 98479d20..bc50f9f1 100644 --- a/Tests/BridgeJSRuntimeTests/ImportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ImportAPITests.swift @@ -34,4 +34,11 @@ class ImportAPITests: XCTestCase { XCTAssertEqual(jsRoundTripString(v), v) } } + + func testClass() { + let greeter = JsGreeter("Alice") + XCTAssertEqual(greeter.greet(), "Hello, Alice!") + greeter.changeName("Bob") + XCTAssertEqual(greeter.greet(), "Hello, Bob!") + } } diff --git a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts index 1a092f90..d2a54f05 100644 --- a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts +++ b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts @@ -2,3 +2,9 @@ export function jsRoundTripVoid(): void export function jsRoundTripNumber(v: number): number export function jsRoundTripBool(v: boolean): boolean export function jsRoundTripString(v: string): string + +export class JsGreeter { + constructor(name: string); + greet(): string; + changeName(name: string): void; +} \ No newline at end of file diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 5de936e1..24a194f9 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -19,6 +19,19 @@ export async function setupOptions(options, context) { "jsRoundTripString": (v) => { return v; }, + JsGreeter: class { + /** @param {string} name */ + constructor(name) { + this.name = name; + } + greet() { + return `Hello, ${this.name}!`; + } + /** @param {string} name */ + changeName(name) { + this.name = name; + } + } }, addToCoreImports(importObject, importsContext) { const { getInstance, getExports } = importsContext; From aa44c4207d12f3c4438e8c9e86922c4eef8eeed8 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 13 Jun 2025 06:20:15 +0000 Subject: [PATCH 11/13] ./Utilities/format.swift --- Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index f44cf2e3..0680a3d3 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -551,7 +551,7 @@ struct BridgeJSLink { importObjectBuilder.appendDts([ "\(type.name): {", "new\(renderTSSignature(parameters: constructor.parameters, returnType: returnType));".indent(count: 4), - "}" + "}", ]) } From 4a3cbb1f65f4515089aaa501a897ad245f73bd24 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 13 Jun 2025 06:55:55 +0000 Subject: [PATCH 12/13] BridgeJS: Support properties in TypeScript classes --- .../Sources/JavaScript/src/processor.js | 3 +- .../Inputs/TypeScriptClass.d.ts | 2 + .../TypeScriptClass.Import.js | 14 ++++++ .../ImportTSTests/TypeScriptClass.swift | 30 +++++++++++++ .../Generated/ImportTS.swift | 43 +++++++++++++++++-- .../Generated/JavaScript/ImportTS.json | 25 +++++++++++ .../BridgeJSRuntimeTests/ImportAPITests.swift | 8 +++- Tests/BridgeJSRuntimeTests/bridge-js.d.ts | 4 +- Tests/prelude.mjs | 10 +++-- 9 files changed, 130 insertions(+), 9 deletions(-) diff --git a/Plugins/BridgeJS/Sources/JavaScript/src/processor.js b/Plugins/BridgeJS/Sources/JavaScript/src/processor.js index d4c72d28..0f97ea14 100644 --- a/Plugins/BridgeJS/Sources/JavaScript/src/processor.js +++ b/Plugins/BridgeJS/Sources/JavaScript/src/processor.js @@ -238,7 +238,8 @@ export class TypeProcessor { for (const member of node.members) { if (ts.isPropertyDeclaration(member)) { - // TODO + const property = this.visitPropertyDecl(member); + if (property) properties.push(property); } else if (ts.isMethodDeclaration(member)) { const decl = this.visitFunctionLikeDecl(member); if (decl) methods.push(decl); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/TypeScriptClass.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/TypeScriptClass.d.ts index d10c0138..074772f2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/TypeScriptClass.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/TypeScriptClass.d.ts @@ -1,4 +1,6 @@ export class Greeter { + name: string; + readonly age: number; constructor(name: string); greet(): string; changeName(name: string): void; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js index 2111af96..19024ed5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js @@ -42,6 +42,20 @@ export async function createInstantiator(options, swift) { let ret = new options.imports.Greeter(nameObject); return swift.memory.retain(ret); } + TestModule["bjs_Greeter_name_get"] = function bjs_Greeter_name_get(self) { + let ret = swift.memory.getObject(self).name; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } + TestModule["bjs_Greeter_name_set"] = function bjs_Greeter_name_set(self, newValue) { + const newValueObject = swift.memory.getObject(newValue); + swift.memory.release(newValue); + swift.memory.getObject(self).name = newValueObject; + } + TestModule["bjs_Greeter_age_get"] = function bjs_Greeter_age_get(self) { + let ret = swift.memory.getObject(self).age; + return ret; + } TestModule["bjs_Greeter_greet"] = function bjs_Greeter_greet(self) { let ret = swift.memory.getObject(self).greet(); tmpRetBytes = textEncoder.encode(ret); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift index 0f1f42d1..e00ae58c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift @@ -37,6 +37,36 @@ struct Greeter { self.this = JSObject(id: UInt32(bitPattern: ret)) } + var name: String { + get { + @_extern(wasm, module: "Check", name: "bjs_Greeter_name_get") + func bjs_Greeter_name_get(_ self: Int32) -> Int32 + let ret = bjs_Greeter_name_get(Int32(bitPattern: self.this.id)) + return String(unsafeUninitializedCapacity: Int(ret)) { b in + _init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) + return Int(ret) + } + } + nonmutating set { + @_extern(wasm, module: "Check", name: "bjs_Greeter_name_set") + func bjs_Greeter_name_set(_ self: Int32, _ newValue: Int32) -> Void + var newValue = newValue + let newValueId = newValue.withUTF8 { b in + _make_jsstring(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + bjs_Greeter_name_set(Int32(bitPattern: self.this.id), newValueId) + } + } + + var age: Double { + get { + @_extern(wasm, module: "Check", name: "bjs_Greeter_age_get") + func bjs_Greeter_age_get(_ self: Int32) -> Float64 + let ret = bjs_Greeter_age_get(Int32(bitPattern: self.this.id)) + return Double(ret) + } + } + func greet() -> String { @_extern(wasm, module: "Check", name: "bjs_Greeter_greet") func bjs_Greeter_greet(_ self: Int32) -> Int32 diff --git a/Tests/BridgeJSRuntimeTests/Generated/ImportTS.swift b/Tests/BridgeJSRuntimeTests/Generated/ImportTS.swift index f479a071..c4b81811 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/ImportTS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/ImportTS.swift @@ -60,17 +60,54 @@ struct JsGreeter { self.this = JSObject(id: UInt32(bitPattern: this)) } - init(_ name: String) { + init(_ name: String, _ prefix: String) { @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_init") - func bjs_JsGreeter_init(_ name: Int32) -> Int32 + func bjs_JsGreeter_init(_ name: Int32, _ prefix: Int32) -> Int32 var name = name let nameId = name.withUTF8 { b in _make_jsstring(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) } - let ret = bjs_JsGreeter_init(nameId) + var prefix = prefix + let prefixId = prefix.withUTF8 { b in + _make_jsstring(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + let ret = bjs_JsGreeter_init(nameId, prefixId) self.this = JSObject(id: UInt32(bitPattern: ret)) } + var name: String { + get { + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_name_get") + func bjs_JsGreeter_name_get(_ self: Int32) -> Int32 + let ret = bjs_JsGreeter_name_get(Int32(bitPattern: self.this.id)) + return String(unsafeUninitializedCapacity: Int(ret)) { b in + _init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) + return Int(ret) + } + } + nonmutating set { + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_name_set") + func bjs_JsGreeter_name_set(_ self: Int32, _ newValue: Int32) -> Void + var newValue = newValue + let newValueId = newValue.withUTF8 { b in + _make_jsstring(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + bjs_JsGreeter_name_set(Int32(bitPattern: self.this.id), newValueId) + } + } + + var prefix: String { + get { + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_prefix_get") + func bjs_JsGreeter_prefix_get(_ self: Int32) -> Int32 + let ret = bjs_JsGreeter_prefix_get(Int32(bitPattern: self.this.id)) + return String(unsafeUninitializedCapacity: Int(ret)) { b in + _init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) + return Int(ret) + } + } + } + func greet() -> String { @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_greet") func bjs_JsGreeter_greet(_ self: Int32) -> Int32 diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/ImportTS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/ImportTS.json index 867957d9..ad8fcd87 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/ImportTS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/ImportTS.json @@ -77,6 +77,14 @@ "type" : { "string" : { + } + } + }, + { + "name" : "prefix", + "type" : { + "string" : { + } } } @@ -115,7 +123,24 @@ ], "name" : "JsGreeter", "properties" : [ + { + "isReadonly" : false, + "name" : "name", + "type" : { + "string" : { + } + } + }, + { + "isReadonly" : true, + "name" : "prefix", + "type" : { + "string" : { + + } + } + } ] } ] diff --git a/Tests/BridgeJSRuntimeTests/ImportAPITests.swift b/Tests/BridgeJSRuntimeTests/ImportAPITests.swift index bc50f9f1..a8d586bf 100644 --- a/Tests/BridgeJSRuntimeTests/ImportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ImportAPITests.swift @@ -36,9 +36,15 @@ class ImportAPITests: XCTestCase { } func testClass() { - let greeter = JsGreeter("Alice") + let greeter = JsGreeter("Alice", "Hello") XCTAssertEqual(greeter.greet(), "Hello, Alice!") greeter.changeName("Bob") XCTAssertEqual(greeter.greet(), "Hello, Bob!") + + greeter.name = "Charlie" + XCTAssertEqual(greeter.greet(), "Hello, Charlie!") + XCTAssertEqual(greeter.name, "Charlie") + + XCTAssertEqual(greeter.prefix, "Hello") } } diff --git a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts index d2a54f05..664dd447 100644 --- a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts +++ b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts @@ -4,7 +4,9 @@ export function jsRoundTripBool(v: boolean): boolean export function jsRoundTripString(v: string): string export class JsGreeter { - constructor(name: string); + name: string; + readonly prefix: string; + constructor(name: string, prefix: string); greet(): string; changeName(name: string): void; } \ No newline at end of file diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 24a194f9..9a97ad9b 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -20,12 +20,16 @@ export async function setupOptions(options, context) { return v; }, JsGreeter: class { - /** @param {string} name */ - constructor(name) { + /** + * @param {string} name + * @param {string} prefix + */ + constructor(name, prefix) { this.name = name; + this.prefix = prefix; } greet() { - return `Hello, ${this.name}!`; + return `${this.prefix}, ${this.name}!`; } /** @param {string} name */ changeName(name) { From 3bf63a1ca489baf99f746e1abe59379a84ab8408 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 13 Jun 2025 08:36:25 +0000 Subject: [PATCH 13/13] BridgeJS: Add support for JSObject in exported Swift interface --- .../Sources/BridgeJSLink/BridgeJSLink.swift | 6 ++++ .../Sources/BridgeJSTool/ExportSwift.swift | 32 +++++++++++++++---- .../Sources/BridgeJSTool/ImportTS.swift | 3 -- .../ArrayParameter.Import.js | 6 ++++ .../BridgeJSLinkTests/Interface.Import.js | 6 ++++ .../PrimitiveParameters.Export.js | 6 ++++ .../PrimitiveParameters.Import.js | 6 ++++ .../PrimitiveReturn.Export.js | 6 ++++ .../PrimitiveReturn.Import.js | 6 ++++ .../StringParameter.Export.js | 6 ++++ .../StringParameter.Import.js | 6 ++++ .../BridgeJSLinkTests/StringReturn.Export.js | 6 ++++ .../BridgeJSLinkTests/StringReturn.Import.js | 6 ++++ .../BridgeJSLinkTests/SwiftClass.Export.js | 6 ++++ .../BridgeJSLinkTests/TypeAlias.Import.js | 6 ++++ .../TypeScriptClass.Import.js | 6 ++++ .../VoidParameterVoidReturn.Export.js | 6 ++++ .../VoidParameterVoidReturn.Import.js | 6 ++++ .../PrimitiveParameters.swift | 6 ++++ .../ExportSwiftTests/PrimitiveReturn.swift | 6 ++++ .../ExportSwiftTests/StringParameter.swift | 6 ++++ .../ExportSwiftTests/StringReturn.swift | 6 ++++ .../ExportSwiftTests/SwiftClass.swift | 6 ++++ .../VoidParameterVoidReturn.swift | 6 ++++ .../ImportTSTests/ArrayParameter.swift | 3 -- .../ImportTSTests/Interface.swift | 3 -- .../ImportTSTests/PrimitiveParameters.swift | 3 -- .../ImportTSTests/PrimitiveReturn.swift | 3 -- .../ImportTSTests/StringParameter.swift | 3 -- .../ImportTSTests/StringReturn.swift | 3 -- .../ImportTSTests/TypeAlias.swift | 3 -- .../ImportTSTests/TypeScriptClass.swift | 3 -- .../VoidParameterVoidReturn.swift | 3 -- Plugins/PackageToJS/Templates/runtime.d.ts | 1 + Plugins/PackageToJS/Templates/runtime.mjs | 4 +++ Runtime/src/memory.ts | 1 + Runtime/src/object-heap.ts | 4 +++ .../BridgeJSRuntimeTests/ExportAPITests.swift | 4 +++ .../Generated/ExportSwift.swift | 13 ++++++++ .../Generated/ImportTS.swift | 3 -- .../Generated/JavaScript/ExportSwift.json | 20 ++++++++++++ Tests/prelude.mjs | 3 ++ 42 files changed, 208 insertions(+), 39 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 0680a3d3..b2bdbe84 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -134,6 +134,12 @@ struct BridgeJSLink { target.set(tmpRetBytes); tmpRetBytes = undefined; } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } \(importObjectBuilders.flatMap { $0.importedLines }.map { $0.indent(count: 12) }.joined(separator: "\n")) }, setInstance: (i) => { diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSTool/ExportSwift.swift index 2e0180fa..25b1ed01 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSTool/ExportSwift.swift @@ -221,11 +221,9 @@ class ExportSwift { return nil } guard let typeDecl = typeDeclResolver.lookupType(for: identifier) else { - print("Failed to lookup type \(type.trimmedDescription): not found in typeDeclResolver") return nil } guard typeDecl.is(ClassDeclSyntax.self) || typeDecl.is(ActorDeclSyntax.self) else { - print("Failed to lookup type \(type.trimmedDescription): is not a class or actor") return nil } return .swiftHeapObject(typeDecl.name.text) @@ -237,10 +235,16 @@ class ExportSwift { // // To update this file, just rebuild your project or run // `swift package bridge-js`. + + @_spi(JSObject_id) import JavaScriptKit + @_extern(wasm, module: "bjs", name: "return_string") private func _return_string(_ ptr: UnsafePointer?, _ len: Int32) @_extern(wasm, module: "bjs", name: "init_memory") private func _init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer?) + + @_extern(wasm, module: "bjs", name: "swift_js_retain") + private func _swift_js_retain(_ ptr: Int32) -> Int32 """ func renderSwiftGlue() -> String? { @@ -317,11 +321,19 @@ class ExportSwift { ) abiParameterSignatures.append((bytesLabel, .i32)) abiParameterSignatures.append((lengthLabel, .i32)) - case .jsObject: + case .jsObject(nil): abiParameterForwardings.append( LabeledExprSyntax( label: param.label, - expression: ExprSyntax("\(raw: param.name)") + expression: ExprSyntax("JSObject(id: UInt32(bitPattern: \(raw: param.name)))") + ) + ) + abiParameterSignatures.append((param.name, .i32)) + case .jsObject(let name): + abiParameterForwardings.append( + LabeledExprSyntax( + label: param.label, + expression: ExprSyntax("\(raw: name)(takingThis: UInt32(bitPattern: \(raw: param.name)))") ) ) abiParameterSignatures.append((param.name, .i32)) @@ -404,10 +416,16 @@ class ExportSwift { } """ ) - case .jsObject: + case .jsObject(nil): + body.append( + """ + return _swift_js_retain(Int32(bitPattern: ret.id)) + """ + ) + case .jsObject(_?): body.append( """ - return ret.id + return _swift_js_retain(Int32(bitPattern: ret.this.id)) """ ) case .swiftHeapObject: @@ -566,6 +584,8 @@ extension BridgeType { self = .bool case "Void": self = .void + case "JSObject": + self = .jsObject(nil) default: return nil } diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSTool/ImportTS.swift index bf269a95..77198dab 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSTool/ImportTS.swift @@ -333,9 +333,6 @@ struct ImportTS { @_extern(wasm, module: "bjs", name: "init_memory_with_result") private func _init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) - - @_extern(wasm, module: "bjs", name: "free_jsobject") - private func _free_jsobject(_ ptr: Int32) -> Void """ func renderSwiftThunk( diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js index caad458d..73ef604f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) { target.set(tmpRetBytes); tmpRetBytes = undefined; } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_checkArray"] = function bjs_checkArray(a) { options.imports.checkArray(swift.memory.getObject(a)); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js index 4b381185..940c565f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) { target.set(tmpRetBytes); tmpRetBytes = undefined; } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_returnAnimatable"] = function bjs_returnAnimatable() { let ret = options.imports.returnAnimatable(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js index 2d9ee4b1..a5b206c5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) { target.set(tmpRetBytes); tmpRetBytes = undefined; } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } }, setInstance: (i) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js index 0d871bbb..7217750a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) { target.set(tmpRetBytes); tmpRetBytes = undefined; } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_check"] = function bjs_check(a, b) { options.imports.check(a, b); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js index 8a66f041..3480cc97 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) { target.set(tmpRetBytes); tmpRetBytes = undefined; } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } }, setInstance: (i) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js index a638f864..5aba76f1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) { target.set(tmpRetBytes); tmpRetBytes = undefined; } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_checkNumber"] = function bjs_checkNumber() { let ret = options.imports.checkNumber(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js index c13cd358..c9397bbd 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) { target.set(tmpRetBytes); tmpRetBytes = undefined; } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } }, setInstance: (i) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js index 6e5d4bdc..5b9808f6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) { target.set(tmpRetBytes); tmpRetBytes = undefined; } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_checkString"] = function bjs_checkString(a) { const aObject = swift.memory.getObject(a); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js index 0208d8ce..caa68521 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) { target.set(tmpRetBytes); tmpRetBytes = undefined; } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } }, setInstance: (i) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js index 26e57959..dfc6f048 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) { target.set(tmpRetBytes); tmpRetBytes = undefined; } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_checkString"] = function bjs_checkString() { let ret = options.imports.checkString(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js index 971b9d69..6b30cd68 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) { target.set(tmpRetBytes); tmpRetBytes = undefined; } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } }, setInstance: (i) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js index e5909f6c..71133762 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) { target.set(tmpRetBytes); tmpRetBytes = undefined; } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_checkSimple"] = function bjs_checkSimple(a) { options.imports.checkSimple(a); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js index 19024ed5..f86e6054 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) { target.set(tmpRetBytes); tmpRetBytes = undefined; } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_Greeter_init"] = function bjs_Greeter_init(name) { const nameObject = swift.memory.getObject(name); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js index a3dae190..166eeed0 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) { target.set(tmpRetBytes); tmpRetBytes = undefined; } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } }, setInstance: (i) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js index db9312aa..91b344c3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) { target.set(tmpRetBytes); tmpRetBytes = undefined; } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_check"] = function bjs_check() { options.imports.check(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.swift index 6df14156..5181eece 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.swift @@ -3,11 +3,17 @@ // // To update this file, just rebuild your project or run // `swift package bridge-js`. + +@_spi(JSObject_id) import JavaScriptKit + @_extern(wasm, module: "bjs", name: "return_string") private func _return_string(_ ptr: UnsafePointer?, _ len: Int32) @_extern(wasm, module: "bjs", name: "init_memory") private func _init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer?) +@_extern(wasm, module: "bjs", name: "swift_js_retain") +private func _swift_js_retain(_ ptr: Int32) -> Int32 + @_expose(wasm, "bjs_check") @_cdecl("bjs_check") public func _bjs_check(a: Int32, b: Float32, c: Float64, d: Int32) -> Void { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.swift index a24b2b31..fb624231 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.swift @@ -3,11 +3,17 @@ // // To update this file, just rebuild your project or run // `swift package bridge-js`. + +@_spi(JSObject_id) import JavaScriptKit + @_extern(wasm, module: "bjs", name: "return_string") private func _return_string(_ ptr: UnsafePointer?, _ len: Int32) @_extern(wasm, module: "bjs", name: "init_memory") private func _init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer?) +@_extern(wasm, module: "bjs", name: "swift_js_retain") +private func _swift_js_retain(_ ptr: Int32) -> Int32 + @_expose(wasm, "bjs_checkInt") @_cdecl("bjs_checkInt") public func _bjs_checkInt() -> Int32 { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.swift index 080f028e..d16cd81c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.swift @@ -3,11 +3,17 @@ // // To update this file, just rebuild your project or run // `swift package bridge-js`. + +@_spi(JSObject_id) import JavaScriptKit + @_extern(wasm, module: "bjs", name: "return_string") private func _return_string(_ ptr: UnsafePointer?, _ len: Int32) @_extern(wasm, module: "bjs", name: "init_memory") private func _init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer?) +@_extern(wasm, module: "bjs", name: "swift_js_retain") +private func _swift_js_retain(_ ptr: Int32) -> Int32 + @_expose(wasm, "bjs_checkString") @_cdecl("bjs_checkString") public func _bjs_checkString(aBytes: Int32, aLen: Int32) -> Void { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.swift index bf0be042..4f3a9e89 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.swift @@ -3,11 +3,17 @@ // // To update this file, just rebuild your project or run // `swift package bridge-js`. + +@_spi(JSObject_id) import JavaScriptKit + @_extern(wasm, module: "bjs", name: "return_string") private func _return_string(_ ptr: UnsafePointer?, _ len: Int32) @_extern(wasm, module: "bjs", name: "init_memory") private func _init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer?) +@_extern(wasm, module: "bjs", name: "swift_js_retain") +private func _swift_js_retain(_ ptr: Int32) -> Int32 + @_expose(wasm, "bjs_checkString") @_cdecl("bjs_checkString") public func _bjs_checkString() -> Void { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift index 20fd9c94..fa0190f7 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift @@ -3,11 +3,17 @@ // // To update this file, just rebuild your project or run // `swift package bridge-js`. + +@_spi(JSObject_id) import JavaScriptKit + @_extern(wasm, module: "bjs", name: "return_string") private func _return_string(_ ptr: UnsafePointer?, _ len: Int32) @_extern(wasm, module: "bjs", name: "init_memory") private func _init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer?) +@_extern(wasm, module: "bjs", name: "swift_js_retain") +private func _swift_js_retain(_ ptr: Int32) -> Int32 + @_expose(wasm, "bjs_takeGreeter") @_cdecl("bjs_takeGreeter") public func _bjs_takeGreeter(greeter: UnsafeMutableRawPointer) -> Void { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.swift index cf4b76fe..a500740c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.swift @@ -3,11 +3,17 @@ // // To update this file, just rebuild your project or run // `swift package bridge-js`. + +@_spi(JSObject_id) import JavaScriptKit + @_extern(wasm, module: "bjs", name: "return_string") private func _return_string(_ ptr: UnsafePointer?, _ len: Int32) @_extern(wasm, module: "bjs", name: "init_memory") private func _init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer?) +@_extern(wasm, module: "bjs", name: "swift_js_retain") +private func _swift_js_retain(_ ptr: Int32) -> Int32 + @_expose(wasm, "bjs_check") @_cdecl("bjs_check") public func _bjs_check() -> Void { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift index 1773223b..2d7ad9f2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift @@ -12,9 +12,6 @@ private func _make_jsstring(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 @_extern(wasm, module: "bjs", name: "init_memory_with_result") private func _init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) -@_extern(wasm, module: "bjs", name: "free_jsobject") -private func _free_jsobject(_ ptr: Int32) -> Void - func checkArray(_ a: JSObject) -> Void { @_extern(wasm, module: "Check", name: "bjs_checkArray") func bjs_checkArray(_ a: Int32) -> Void diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift index c565a2f8..85f12665 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift @@ -12,9 +12,6 @@ private func _make_jsstring(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 @_extern(wasm, module: "bjs", name: "init_memory_with_result") private func _init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) -@_extern(wasm, module: "bjs", name: "free_jsobject") -private func _free_jsobject(_ ptr: Int32) -> Void - func returnAnimatable() -> Animatable { @_extern(wasm, module: "Check", name: "bjs_returnAnimatable") func bjs_returnAnimatable() -> Int32 diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift index 4ab7f754..401d78b8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift @@ -12,9 +12,6 @@ private func _make_jsstring(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 @_extern(wasm, module: "bjs", name: "init_memory_with_result") private func _init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) -@_extern(wasm, module: "bjs", name: "free_jsobject") -private func _free_jsobject(_ ptr: Int32) -> Void - func check(_ a: Double, _ b: Bool) -> Void { @_extern(wasm, module: "Check", name: "bjs_check") func bjs_check(_ a: Float64, _ b: Int32) -> Void diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift index a60c9323..da9bfc3b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift @@ -12,9 +12,6 @@ private func _make_jsstring(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 @_extern(wasm, module: "bjs", name: "init_memory_with_result") private func _init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) -@_extern(wasm, module: "bjs", name: "free_jsobject") -private func _free_jsobject(_ ptr: Int32) -> Void - func checkNumber() -> Double { @_extern(wasm, module: "Check", name: "bjs_checkNumber") func bjs_checkNumber() -> Float64 diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift index 491978bc..85852bd2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift @@ -12,9 +12,6 @@ private func _make_jsstring(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 @_extern(wasm, module: "bjs", name: "init_memory_with_result") private func _init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) -@_extern(wasm, module: "bjs", name: "free_jsobject") -private func _free_jsobject(_ ptr: Int32) -> Void - func checkString(_ a: String) -> Void { @_extern(wasm, module: "Check", name: "bjs_checkString") func bjs_checkString(_ a: Int32) -> Void diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringReturn.swift index ce32a643..4702c5a9 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringReturn.swift @@ -12,9 +12,6 @@ private func _make_jsstring(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 @_extern(wasm, module: "bjs", name: "init_memory_with_result") private func _init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) -@_extern(wasm, module: "bjs", name: "free_jsobject") -private func _free_jsobject(_ ptr: Int32) -> Void - func checkString() -> String { @_extern(wasm, module: "Check", name: "bjs_checkString") func bjs_checkString() -> Int32 diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeAlias.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeAlias.swift index 79f29c92..2c7a8c7f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeAlias.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeAlias.swift @@ -12,9 +12,6 @@ private func _make_jsstring(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 @_extern(wasm, module: "bjs", name: "init_memory_with_result") private func _init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) -@_extern(wasm, module: "bjs", name: "free_jsobject") -private func _free_jsobject(_ ptr: Int32) -> Void - func checkSimple(_ a: Double) -> Void { @_extern(wasm, module: "Check", name: "bjs_checkSimple") func bjs_checkSimple(_ a: Float64) -> Void diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift index e00ae58c..3dc779ae 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift @@ -12,9 +12,6 @@ private func _make_jsstring(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 @_extern(wasm, module: "bjs", name: "init_memory_with_result") private func _init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) -@_extern(wasm, module: "bjs", name: "free_jsobject") -private func _free_jsobject(_ ptr: Int32) -> Void - struct Greeter { let this: JSObject diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/VoidParameterVoidReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/VoidParameterVoidReturn.swift index 3f2ecc78..71cee5dc 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/VoidParameterVoidReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/VoidParameterVoidReturn.swift @@ -12,9 +12,6 @@ private func _make_jsstring(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 @_extern(wasm, module: "bjs", name: "init_memory_with_result") private func _init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) -@_extern(wasm, module: "bjs", name: "free_jsobject") -private func _free_jsobject(_ ptr: Int32) -> Void - func check() -> Void { @_extern(wasm, module: "Check", name: "bjs_check") func bjs_check() -> Void diff --git a/Plugins/PackageToJS/Templates/runtime.d.ts b/Plugins/PackageToJS/Templates/runtime.d.ts index 9613004c..ed94f7e4 100644 --- a/Plugins/PackageToJS/Templates/runtime.d.ts +++ b/Plugins/PackageToJS/Templates/runtime.d.ts @@ -8,6 +8,7 @@ declare class Memory { retain: (value: any) => number; getObject: (ref: number) => any; release: (ref: number) => void; + retainByRef: (ref: number) => number; bytes: () => Uint8Array; dataView: () => DataView; writeBytes: (ptr: pointer, bytes: Uint8Array) => void; diff --git a/Plugins/PackageToJS/Templates/runtime.mjs b/Plugins/PackageToJS/Templates/runtime.mjs index 71f7f9a3..e3673835 100644 --- a/Plugins/PackageToJS/Templates/runtime.mjs +++ b/Plugins/PackageToJS/Templates/runtime.mjs @@ -158,6 +158,9 @@ class SwiftRuntimeHeap { this._heapEntryByValue.set(value, { id: id, rc: 1 }); return id; } + retainByRef(ref) { + return this.retain(this.referenceHeap(ref)); + } release(ref) { const value = this._heapValueById.get(ref); const entry = this._heapEntryByValue.get(value); @@ -182,6 +185,7 @@ class Memory { this.retain = (value) => this.heap.retain(value); this.getObject = (ref) => this.heap.referenceHeap(ref); this.release = (ref) => this.heap.release(ref); + this.retainByRef = (ref) => this.heap.retainByRef(ref); this.bytes = () => new Uint8Array(this.rawMemory.buffer); this.dataView = () => new DataView(this.rawMemory.buffer); this.writeBytes = (ptr, bytes) => this.bytes().set(bytes, ptr); diff --git a/Runtime/src/memory.ts b/Runtime/src/memory.ts index d8334516..5ba00c82 100644 --- a/Runtime/src/memory.ts +++ b/Runtime/src/memory.ts @@ -13,6 +13,7 @@ export class Memory { retain = (value: any) => this.heap.retain(value); getObject = (ref: number) => this.heap.referenceHeap(ref); release = (ref: number) => this.heap.release(ref); + retainByRef = (ref: number) => this.heap.retainByRef(ref); bytes = () => new Uint8Array(this.rawMemory.buffer); dataView = () => new DataView(this.rawMemory.buffer); diff --git a/Runtime/src/object-heap.ts b/Runtime/src/object-heap.ts index d59f5101..a239cf2b 100644 --- a/Runtime/src/object-heap.ts +++ b/Runtime/src/object-heap.ts @@ -33,6 +33,10 @@ export class SwiftRuntimeHeap { return id; } + retainByRef(ref: ref) { + return this.retain(this.referenceHeap(ref)); + } + release(ref: ref) { const value = this._heapValueById.get(ref); const entry = this._heapEntryByValue.get(value)!; diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index 8449b06d..e113a514 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -28,6 +28,10 @@ func runJsWorks() -> Void return v } +@JS func roundTripJSObject(v: JSObject) -> JSObject { + return v +} + @JS class Greeter { var name: String diff --git a/Tests/BridgeJSRuntimeTests/Generated/ExportSwift.swift b/Tests/BridgeJSRuntimeTests/Generated/ExportSwift.swift index 4a7c262c..28514c8e 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/ExportSwift.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/ExportSwift.swift @@ -3,11 +3,17 @@ // // To update this file, just rebuild your project or run // `swift package bridge-js`. + +@_spi(JSObject_id) import JavaScriptKit + @_extern(wasm, module: "bjs", name: "return_string") private func _return_string(_ ptr: UnsafePointer?, _ len: Int32) @_extern(wasm, module: "bjs", name: "init_memory") private func _init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer?) +@_extern(wasm, module: "bjs", name: "swift_js_retain") +private func _swift_js_retain(_ ptr: Int32) -> Int32 + @_expose(wasm, "bjs_roundTripVoid") @_cdecl("bjs_roundTripVoid") public func _bjs_roundTripVoid() -> Void { @@ -62,6 +68,13 @@ public func _bjs_roundTripSwiftHeapObject(v: UnsafeMutableRawPointer) -> UnsafeM return Unmanaged.passRetained(ret).toOpaque() } +@_expose(wasm, "bjs_roundTripJSObject") +@_cdecl("bjs_roundTripJSObject") +public func _bjs_roundTripJSObject(v: Int32) -> Int32 { + let ret = roundTripJSObject(v: JSObject(id: UInt32(bitPattern: v))) + return _swift_js_retain(Int32(bitPattern: ret.id)) +} + @_expose(wasm, "bjs_takeGreeter") @_cdecl("bjs_takeGreeter") public func _bjs_takeGreeter(g: UnsafeMutableRawPointer, nameBytes: Int32, nameLen: Int32) -> Void { diff --git a/Tests/BridgeJSRuntimeTests/Generated/ImportTS.swift b/Tests/BridgeJSRuntimeTests/Generated/ImportTS.swift index c4b81811..c01a0fce 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/ImportTS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/ImportTS.swift @@ -12,9 +12,6 @@ private func _make_jsstring(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 @_extern(wasm, module: "bjs", name: "init_memory_with_result") private func _init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) -@_extern(wasm, module: "bjs", name: "free_jsobject") -private func _free_jsobject(_ ptr: Int32) -> Void - func jsRoundTripVoid() -> Void { @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsRoundTripVoid") func bjs_jsRoundTripVoid() -> Void diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/ExportSwift.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/ExportSwift.json index b4ab9701..d72c17b9 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/ExportSwift.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/ExportSwift.json @@ -185,6 +185,26 @@ } } }, + { + "abiName" : "bjs_roundTripJSObject", + "name" : "roundTripJSObject", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "jsObject" : { + + } + } + } + ], + "returnType" : { + "jsObject" : { + + } + } + }, { "abiName" : "bjs_takeGreeter", "name" : "takeGreeter", diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 9a97ad9b..c79feb2a 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -102,6 +102,9 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { exports.takeGreeter(g, "Jay"); assert.equal(g.greet(), "Hello, Jay!"); g.release(); + + const anyObject = {}; + assert.equal(exports.roundTripJSObject(anyObject), anyObject); } function setupTestGlobals(global) {