diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2766d6ef..5375b3e4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -88,6 +88,24 @@ jobs: echo "::error::The formatting changed some files. Please run \`./Utilities/format.swift\` and commit the changes." exit 1 } + + check-bridgejs-generated: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v5 + - uses: ./.github/actions/install-swift + with: + download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2025-06-12-a/swift-DEVELOPMENT-SNAPSHOT-2025-06-12-a-ubuntu22.04.tar.gz + - run: make bootstrap + - run: ./Utilities/bridge-js-generate.sh + - name: Check if BridgeJS generated files are up-to-date + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + git diff --exit-code || { + echo "::error::BridgeJS generated files are out of date. Please run \`./Utilities/bridge-js-generate.sh\` and commit the changes." + exit 1 + } + build-examples: runs-on: ubuntu-latest steps: @@ -107,7 +125,7 @@ jobs: SWIFT_SDK_ID_wasm32_unknown_wasi: ${{ steps.setup-wasm32-unknown-wasi.outputs.swift-sdk-id }} - name: Upload static files as artifact id: deployment - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v4 with: path: Examples/ deploy-examples: diff --git a/.gitignore b/.gitignore index 5aac0048..a62100fd 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ Examples/*/Bundle Examples/*/package-lock.json Package.resolved Plugins/BridgeJS/Sources/JavaScript/package-lock.json +Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/**/*.actual +bridge-js.config.local.json diff --git a/Benchmarks/README.md b/Benchmarks/README.md index eeafc395..f7264b6b 100644 --- a/Benchmarks/README.md +++ b/Benchmarks/README.md @@ -27,4 +27,11 @@ node run.js --adaptive --output=stable-results.json # Run benchmarks and compare with previous results node run.js --baseline=previous-results.json + +# Run only a subset of benchmarks +# Substring match +node run.js --filter=Call +# Regex (with flags) +node run.js --filter=/^Property access\// +node run.js --filter=/string/i ``` diff --git a/Benchmarks/Sources/Benchmarks.swift b/Benchmarks/Sources/Benchmarks.swift index 155acae1..55b9f3eb 100644 --- a/Benchmarks/Sources/Benchmarks.swift +++ b/Benchmarks/Sources/Benchmarks.swift @@ -14,7 +14,7 @@ class Benchmark { body() return .undefined } - benchmarkRunner("\(title)/\(name)", jsBody) + try! benchmarkRunner("\(title)/\(name)", jsBody) } } @@ -24,13 +24,13 @@ class Benchmark { call.testSuite("JavaScript function call through Wasm import") { for _ in 0..<20_000_000 { - benchmarkHelperNoop() + try! benchmarkHelperNoop() } } call.testSuite("JavaScript function call through Wasm import with int") { for _ in 0..<10_000_000 { - benchmarkHelperNoopWithNumber(42) + try! benchmarkHelperNoopWithNumber(42) } } diff --git a/Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift b/Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift index 871938cf..bc7f0b17 100644 --- a/Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift +++ b/Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift @@ -30,7 +30,7 @@ func benchmarkHelperNoopWithNumber(_ n: Double) throws(JSException) -> Void { fatalError("Only available on WebAssembly") } #endif - bjs_benchmarkHelperNoopWithNumber(n) + bjs_benchmarkHelperNoopWithNumber(n.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -45,11 +45,7 @@ func benchmarkRunner(_ name: String, _ body: JSObject) throws(JSException) -> Vo fatalError("Only available on WebAssembly") } #endif - var name = name - let nameId = name.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_benchmarkRunner(nameId, Int32(bitPattern: body.id)) + bjs_benchmarkRunner(name.bridgeJSLowerParameter(), body.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json b/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json index f0fd49e5..b00ec9ab 100644 --- a/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -1,6 +1,9 @@ { "classes" : [ + ], + "enums" : [ + ], "functions" : [ { @@ -19,5 +22,6 @@ } } } - ] + ], + "moduleName" : "Benchmarks" } \ No newline at end of file diff --git a/Benchmarks/run.js b/Benchmarks/run.js index 2305373a..17b19214 100644 --- a/Benchmarks/run.js +++ b/Benchmarks/run.js @@ -21,6 +21,32 @@ function updateProgress(current, total, label = '', width) { process.stdout.write(`${label} [${bar}] ${current}/${total}`); } +/** + * Create a name filter function from CLI argument + * - Supports substring match (default) + * - Supports /regex/flags syntax + * @param {string|undefined} arg + * @returns {(name: string) => boolean} + */ +function createNameFilter(arg) { + if (!arg) { + return () => true; + } + if (arg.startsWith('/') && arg.lastIndexOf('/') > 0) { + const lastSlash = arg.lastIndexOf('/'); + const pattern = arg.slice(1, lastSlash); + const flags = arg.slice(lastSlash + 1); + try { + const re = new RegExp(pattern, flags); + return (name) => re.test(name); + } catch (e) { + console.error('Invalid regular expression for --filter:', e.message); + process.exit(1); + } + } + return (name) => name.includes(arg); +} + /** * Calculate coefficient of variation (relative standard deviation) * @param {Array} values - Array of measurement values @@ -238,24 +264,28 @@ function saveJsonResults(filePath, data) { * @param {Object} results - Results object to store benchmark data * @returns {Promise} */ -async function singleRun(results) { +async function singleRun(results, nameFilter) { const options = await defaultNodeSetup({}) + const benchmarkRunner = (name, body) => { + if (nameFilter && !nameFilter(name)) { + return; + } + const startTime = performance.now(); + body(); + const endTime = performance.now(); + const duration = endTime - startTime; + if (!results[name]) { + results[name] = [] + } + results[name].push(duration) + } const { exports } = await instantiate({ ...options, - imports: { + getImports: () => ({ benchmarkHelperNoop: () => { }, benchmarkHelperNoopWithNumber: (n) => { }, - benchmarkRunner: (name, body) => { - const startTime = performance.now(); - body(); - const endTime = performance.now(); - const duration = endTime - startTime; - if (!results[name]) { - results[name] = [] - } - results[name].push(duration) - } - } + benchmarkRunner: benchmarkRunner + }) }); exports.run(); } @@ -266,7 +296,7 @@ async function singleRun(results) { * @param {Object} options - Adaptive sampling options * @returns {Promise} */ -async function runUntilStable(results, options, width) { +async function runUntilStable(results, options, width, nameFilter, filterArg) { const { minRuns = 5, maxRuns = 50, @@ -285,9 +315,14 @@ async function runUntilStable(results, options, width) { // Update progress with estimated completion updateProgress(runs, maxRuns, "Benchmark Progress:", width); - await singleRun(results); + await singleRun(results, nameFilter); runs++; + if (runs === 1 && Object.keys(results).length === 0) { + console.error(`\nNo benchmarks matched filter: ${filterArg}`); + process.exit(1); + } + // Check if we've reached minimum runs if (runs < minRuns) continue; @@ -349,6 +384,7 @@ Options: --min-runs=NUMBER Minimum runs for adaptive sampling (default: 5) --max-runs=NUMBER Maximum runs for adaptive sampling (default: 50) --target-cv=NUMBER Target coefficient of variation % (default: 5) + --filter=PATTERN Filter benchmarks by name (substring or /regex/flags) --help Show this help message `); } @@ -363,7 +399,8 @@ async function main() { adaptive: { type: 'boolean', default: false }, 'min-runs': { type: 'string', default: '5' }, 'max-runs': { type: 'string', default: '50' }, - 'target-cv': { type: 'string', default: '5' } + 'target-cv': { type: 'string', default: '5' }, + filter: { type: 'string' } } }); @@ -374,6 +411,8 @@ async function main() { const results = {}; const width = 30; + const filterArg = args.values.filter; + const nameFilter = createNameFilter(filterArg); if (args.values.adaptive) { // Adaptive sampling mode @@ -388,7 +427,7 @@ async function main() { console.log(`Results will be saved to: ${args.values.output}`); } - await runUntilStable(results, options, width); + await runUntilStable(results, options, width, nameFilter, filterArg); } else { // Fixed number of runs mode const runs = parseInt(args.values.runs, 10); @@ -410,7 +449,12 @@ async function main() { console.log("\nOverall Progress:"); for (let i = 0; i < runs; i++) { updateProgress(i, runs, "Benchmark Runs:", width); - await singleRun(results); + await singleRun(results, nameFilter); + if (i === 0 && Object.keys(results).length === 0) { + process.stdout.write("\n"); + console.error(`No benchmarks matched filter: ${filterArg}`); + process.exit(1); + } } updateProgress(runs, runs, "Benchmark Runs:", width); console.log("\n"); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f71ca83a..666b62d2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,6 +71,18 @@ Tests for `PackageToJS` plugin: swift test --package-path ./Plugins/PackageToJS ``` +Tests for `BridgeJS` plugin: + +```bash +swift test --package-path ./Plugins/BridgeJS +``` + +To update snapshot test files when expected output changes: + +```bash +UPDATE_SNAPSHOTS=1 swift test --package-path ./Plugins/BridgeJS +``` + ### Editing `./Runtime` directory The `./Runtime` directory contains the JavaScript runtime that interacts with the JavaScript environment and Swift code. @@ -81,5 +93,24 @@ To make changes to the runtime, you need to edit the TypeScript files and regene make regenerate_swiftpm_resources ``` +### Working with BridgeJS + +BridgeJS is a Swift Package Manager plugin that automatically generates Swift bindings from TypeScript definitions. This repository contains pre-generated files created by BridgeJS in AoT (Ahead of Time) mode that are checked into version control. + +To update these pre-generated files, use the utility script: + +```bash +./Utilities/bridge-js-generate.sh +``` + +This script runs the BridgeJS plugin in AoT mode (`swift package bridge-js`) on several SwiftPM packages in this repository. + +Run this script when you've made changes to: +- TypeScript definitions +- BridgeJS configuration +- BridgeJS code generator itself + +These changes require updating the pre-generated Swift bindings committed to the repository. + ## Support If you have any questions or need assistance, feel free to reach out via [GitHub Issues](https://github.com/swiftwasm/JavaScriptKit/issues) or [Discord](https://discord.gg/ashJW8T8yp). diff --git a/Examples/ImportTS/Package.swift b/Examples/ImportTS/Package.swift index 4809ec00..fdcf09b7 100644 --- a/Examples/ImportTS/Package.swift +++ b/Examples/ImportTS/Package.swift @@ -5,7 +5,7 @@ import PackageDescription let package = Package( name: "MyApp", platforms: [ - .macOS(.v10_15), + .macOS(.v11), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), diff --git a/Examples/ImportTS/Sources/main.swift b/Examples/ImportTS/Sources/main.swift index 4853a966..79654032 100644 --- a/Examples/ImportTS/Sources/main.swift +++ b/Examples/ImportTS/Sources/main.swift @@ -2,25 +2,25 @@ import JavaScriptKit // This function is automatically generated by the @JS plugin // It demonstrates how to use TypeScript functions and types imported from bridge-js.d.ts -@JS public func run() { +@JS public func run() throws(JSException) { // Call the imported consoleLog function defined in bridge-js.d.ts - consoleLog("Hello, World!") + try consoleLog("Hello, World!") // Get the document object - this comes from the imported getDocument() function - let document = getDocument() + let document = try getDocument() // Access and modify properties - the title property is read/write - document.title = "Hello, World!" + try document.setTitle("Hello, World!") // Access read-only properties - body is defined as readonly in TypeScript - let body = document.body + let body = try document.body // Create a new element using the document.createElement method - let h1 = document.createElement("h1") + let h1 = try document.createElement("h1") // Set properties on the created element - h1.innerText = "Hello, World!" + try h1.setInnerText("Hello, World!") // Call methods on objects - appendChild is defined in the HTMLElement interface - body.appendChild(h1) + try body.appendChild(h1) } diff --git a/Examples/ImportTS/index.js b/Examples/ImportTS/index.js index 9452b7ec..f0b81e3b 100644 --- a/Examples/ImportTS/index.js +++ b/Examples/ImportTS/index.js @@ -1,12 +1,14 @@ import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; const { exports } = await init({ - imports: { - consoleLog: (message) => { - console.log(message); - }, - getDocument: () => { - return document; - }, + getImports() { + return { + consoleLog: (message) => { + console.log(message); + }, + getDocument: () => { + return document; + }, + } } }); diff --git a/Examples/PlayBridgeJS/README.md b/Examples/PlayBridgeJS/README.md index 930f07c9..85c5a6f9 100644 --- a/Examples/PlayBridgeJS/README.md +++ b/Examples/PlayBridgeJS/README.md @@ -4,6 +4,6 @@ Install Development Snapshot toolchain `DEVELOPMENT-SNAPSHOT-2024-07-08-a` from ```sh $ swift sdk install https://github.com/swiftwasm/swift/releases/download/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-07-09-a/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-07-09-a-wasm32-unknown-wasi.artifactbundle.zip -$ ./build.sh -$ npx serve +$ ./build.sh release +$ npx serve --symlinks ``` diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/app.js b/Examples/PlayBridgeJS/Sources/JavaScript/app.js index b14db79b..2f5267d3 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/app.js +++ b/Examples/PlayBridgeJS/Sources/JavaScript/app.js @@ -1,44 +1,98 @@ -// BridgeJS Playground Main Application +// @ts-check import { EditorSystem } from './editor.js'; import ts from 'typescript'; import { TypeProcessor } from './processor.js'; +import { CodeShareManager } from './code-share.js'; +import { + createSystem, + createDefaultMapFromCDN, + createVirtualCompilerHost +} from '@typescript/vfs'; +/** + * @typedef {import('../../Bundle/bridge-js.js').PlayBridgeJS} PlayBridgeJS + */ + +/** + * The main controller for the BridgeJS Playground. + */ export class BridgeJSPlayground { + /** + * Creates a new instance of the BridgeJSPlayground. + */ constructor() { this.editorSystem = new EditorSystem(); + /** @type {PlayBridgeJS | null} */ this.playBridgeJS = null; + /** @type {ReturnType | null} */ this.generateTimeout = null; + /** @type {boolean} */ this.isInitialized = false; - // DOM Elements - this.errorDisplay = document.getElementById('errorDisplay'); - this.errorMessage = document.getElementById('errorMessage'); + /** @type {HTMLDivElement} */ + this.errorDisplay = /** @type {HTMLDivElement} */ (document.getElementById('errorDisplay')); + /** @type {HTMLDivElement} */ + this.errorMessage = /** @type {HTMLDivElement} */ (document.getElementById('errorMessage')); + /** @type {HTMLButtonElement} */ + this.shareButton = /** @type {HTMLButtonElement} */ (document.getElementById('shareButton')); + /** @type {HTMLDialogElement} */ + this.shareDialog = /** @type {HTMLDialogElement} */ (document.getElementById('shareDialog')); + /** @type {HTMLInputElement} */ + this.shareUrlInput = /** @type {HTMLInputElement} */ (document.getElementById('shareUrl')); + /** @type {HTMLButtonElement} */ + this.copyButton = /** @type {HTMLButtonElement} */ (document.getElementById('copyButton')); + /** @type {HTMLButtonElement} */ + this.closeShareDialogButton = /** @type {HTMLButtonElement} */ (document.getElementById('closeShareDialog')); + + // Progress UI elements + /** @type {HTMLDivElement | null} */ + this.progressBar = /** @type {HTMLDivElement} */ (document.getElementById('progressBar')); + /** @type {HTMLDivElement | null} */ + this.progressFill = /** @type {HTMLDivElement} */ (document.getElementById('progressFill')); + /** @type {HTMLDivElement | null} */ + this.progressLabel = /** @type {HTMLDivElement} */ (document.getElementById('progressLabel')); } - // Initialize the application - async initialize() { + /** + * Initializes the application. + * @param {{swift: string, dts: string}} sampleCode - The sample code to initialize the application with. + */ + async initialize(sampleCode) { if (this.isInitialized) { return; } try { + this.showProgress('Starting…', 5); // Initialize editor system await this.editorSystem.init(); + this.setProgress('Editor ready', 30); // Initialize BridgeJS await this.initializeBridgeJS(); + this.setProgress('BridgeJS ready', 70); // Set up event listeners this.setupEventListeners(); - // Load sample code - this.editorSystem.loadSampleCode(); - + // Check for shared code in URL + this.setProgress('Checking shared code…', 80); + const sharedCode = await CodeShareManager.extractCodeFromUrl(); + if (sharedCode) { + this.editorSystem.setInputs(sharedCode); + } else { + // Load sample code + this.editorSystem.setInputs(sampleCode); + } + this.setProgress('Finalizing…', 95); this.isInitialized = true; console.log('BridgeJS Playground initialized successfully'); + this.setProgress('Ready', 100); + setTimeout(() => this.hideProgress(), 400); } catch (error) { console.error('Failed to initialize BridgeJS Playground:', error); this.showError('Failed to initialize application: ' + error.message); + this.hideProgress(); } } @@ -46,12 +100,16 @@ export class BridgeJSPlayground { async initializeBridgeJS() { try { // Import the BridgeJS module - const { init } = await import("../../.build/plugins/PackageToJS/outputs/Package/index.js"); + this.setProgress('Loading BridgeJS…', 50); + const { init } = await import("../../Bundle/index.js"); + const virtualHost = await this.createTS2SkeletonFactory(); + this.setProgress('Preparing TypeScript host…', 60); const { exports } = await init({ - imports: { - createTS2Skeleton: this.createTS2Skeleton - } + getImports: () => ({ + createTS2Skeleton: () => this.createTS2Skeleton(virtualHost) + }) }); + this.setProgress('Creating runtime…', 65); this.playBridgeJS = new exports.PlayBridgeJS(); console.log('BridgeJS initialized successfully'); } catch (error) { @@ -70,34 +128,109 @@ export class BridgeJSPlayground { } this.generateTimeout = setTimeout(() => this.generateCode(), 300); }); + + // Set up share functionality + this.setupShareListeners(); + } + + // Set up share-related event listeners + setupShareListeners() { + // Show share dialog + this.shareButton.addEventListener('click', async () => { + try { + const inputs = this.editorSystem.getInputs(); + const shareUrl = await CodeShareManager.generateShareUrl(inputs); + this.shareUrlInput.value = shareUrl; + this.shareDialog.classList.remove('hidden'); + this.shareUrlInput.select(); + } catch (error) { + console.error('Failed to generate share URL:', error); + this.showError('Failed to generate share URL: ' + error.message); + } + }); + + // Copy share URL + this.copyButton.addEventListener('click', async () => { + try { + await navigator.clipboard.writeText(this.shareUrlInput.value); + + const originalText = this.copyButton.textContent; + this.copyButton.textContent = 'Copied!'; + this.copyButton.classList.add('copied'); + + setTimeout(() => { + this.copyButton.textContent = originalText; + this.copyButton.classList.remove('copied'); + }, 2000); + } catch (error) { + console.error('Failed to copy URL:', error); + this.shareUrlInput.select(); + } + }); + + // Close share dialog + this.closeShareDialogButton.addEventListener('click', () => { + this.shareDialog.classList.add('hidden'); + }); + + // Close dialog when clicking outside + document.addEventListener('click', (event) => { + if (!this.shareDialog.classList.contains('hidden')) { + const dialogContent = this.shareDialog.querySelector('.share-dialog-content'); + const target = event.target; + if (dialogContent && target instanceof Node && !dialogContent.contains(target) && + this.shareButton && !this.shareButton.contains(target)) { + this.shareDialog.classList.add('hidden'); + } + } + }); + + // Close dialog with Escape key + document.addEventListener('keydown', (event) => { + if (event.key === 'Escape' && !this.shareDialog.classList.contains('hidden')) { + this.shareDialog.classList.add('hidden'); + } + }); } - createTS2Skeleton() { + async createTS2SkeletonFactory() { + const createVirtualHost = async () => { + const fsMap = await createDefaultMapFromCDN( + { target: ts.ScriptTarget.ES2015 }, + ts.version, + true, + ts + ); + + const system = createSystem(fsMap); + + const compilerOptions = { + target: ts.ScriptTarget.ES2015, + lib: ["es2015", "dom"], + }; + + return createVirtualCompilerHost(system, compilerOptions, ts); + } + return await createVirtualHost(); + } + + /** + * @param {ReturnType} virtualHost + */ + createTS2Skeleton(virtualHost) { return { + /** + * @param {string} dtsCode + * @returns {string} + */ convert: (dtsCode) => { - const virtualFilePath = "bridge-js.d.ts" - const virtualHost = { - fileExists: fileName => fileName === virtualFilePath, - readFile: fileName => dtsCode, - getSourceFile: (fileName, languageVersion) => { - const sourceText = dtsCode; - if (sourceText === undefined) return undefined; - return ts.createSourceFile(fileName, sourceText, languageVersion); - }, - getDefaultLibFileName: options => "lib.d.ts", - writeFile: (fileName, data) => { - console.log(`[emit] ${fileName}:\n${data}`); - }, - getCurrentDirectory: () => "", - getDirectories: () => [], - getCanonicalFileName: fileName => fileName, - getNewLine: () => "\n", - useCaseSensitiveFileNames: () => true - } // Create TypeScript program from d.ts content + const virtualFilePath = "bridge-js.d.ts" + const sourceFile = ts.createSourceFile(virtualFilePath, dtsCode, ts.ScriptTarget.ES2015); + virtualHost.updateFile(sourceFile); const tsProgram = ts.createProgram({ rootNames: [virtualFilePath], - host: virtualHost, + host: virtualHost.compilerHost, options: { noEmit: true, declaration: true, @@ -124,7 +257,9 @@ export class BridgeJSPlayground { } } - // Generate code through BridgeJS + /** + * Generates code through BridgeJS. + */ async generateCode() { if (!this.playBridgeJS) { this.showError('BridgeJS is not initialized'); @@ -152,14 +287,57 @@ export class BridgeJSPlayground { } } - // Show error message + /** + * Shows an error message. + * @param {string} message - The message to show. + */ showError(message) { this.errorMessage.textContent = message; this.errorDisplay.classList.add('show'); } - // Hide error message + /** + * Hides the error message. + */ hideError() { this.errorDisplay.classList.remove('show'); } + + /** + * Shows progress bar. + * @param {string} label + * @param {number} percent + */ + showProgress(label, percent) { + if (this.progressBar) { + this.progressBar.classList.add('show'); + this.progressBar.classList.remove('hidden'); + } + this.setProgress(label, percent); + } + + /** + * Updates progress label and percentage. + * @param {string} label + * @param {number} percent + */ + setProgress(label, percent) { + if (this.progressLabel) { + this.progressLabel.textContent = label; + } + if (this.progressFill) { + const clamped = Math.max(0, Math.min(100, Number(percent) || 0)); + this.progressFill.style.width = clamped + '%'; + } + } + + /** + * Hides progress bar. + */ + hideProgress() { + if (this.progressBar) { + this.progressBar.classList.remove('show'); + this.progressBar.classList.add('hidden'); + } + } } \ No newline at end of file diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/code-share.js b/Examples/PlayBridgeJS/Sources/JavaScript/code-share.js new file mode 100644 index 00000000..61d6ee95 --- /dev/null +++ b/Examples/PlayBridgeJS/Sources/JavaScript/code-share.js @@ -0,0 +1,189 @@ +// @ts-check + +export class CodeCompression { + /** + * Compresses a string using gzip compression and returns base64-encoded result. + * @param {string} text - The text to compress + * @returns {Promise} Base64-encoded compressed string + */ + static async compress(text) { + const textEncoder = new TextEncoder(); + const stream = new CompressionStream('gzip'); + const writer = stream.writable.getWriter(); + const reader = stream.readable.getReader(); + + // Start compression + const writePromise = writer.write(textEncoder.encode(text)).then(() => writer.close()); + + // Read compressed chunks + const chunks = []; + let readResult; + do { + readResult = await reader.read(); + if (readResult.value) { + chunks.push(readResult.value); + } + } while (!readResult.done); + + await writePromise; + + // Combine all chunks into single Uint8Array + const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0); + const compressed = new Uint8Array(totalLength); + let offset = 0; + for (const chunk of chunks) { + compressed.set(chunk, offset); + offset += chunk.length; + } + + // Convert to base64 for URL safety + return this.uint8ArrayToBase64(compressed); + } + + /** + * Decompresses a base64-encoded gzip string back to original text. + * @param {string} compressedBase64 - Base64-encoded compressed string + * @returns {Promise} Original decompressed text + */ + static async decompress(compressedBase64) { + const compressed = this.base64ToUint8Array(compressedBase64); + const stream = new DecompressionStream('gzip'); + const writer = stream.writable.getWriter(); + const reader = stream.readable.getReader(); + + // Start decompression + const writePromise = writer.write(compressed).then(() => writer.close()); + + // Read decompressed chunks + const chunks = []; + let readResult; + do { + readResult = await reader.read(); + if (readResult.value) { + chunks.push(readResult.value); + } + } while (!readResult.done); + + await writePromise; + + // Combine chunks and decode + const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0); + const decompressed = new Uint8Array(totalLength); + let offset = 0; + for (const chunk of chunks) { + decompressed.set(chunk, offset); + offset += chunk.length; + } + + const textDecoder = new TextDecoder(); + return textDecoder.decode(decompressed); + } + + /** + * Converts Uint8Array to base64 string. + * @param {Uint8Array} uint8Array - Array to convert + * @returns {string} Base64 string + */ + static uint8ArrayToBase64(uint8Array) { + let binary = ''; + for (let i = 0; i < uint8Array.byteLength; i++) { + binary += String.fromCharCode(uint8Array[i]); + } + return btoa(binary); + } + + /** + * Converts base64 string to Uint8Array. + * @param {string} base64 - Base64 string to convert + * @returns {Uint8Array} Converted array + */ + static base64ToUint8Array(base64) { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + return bytes; + } +} + +/** + * URL parameter manager for sharing code. + * Handles compression, URL generation, and parameter extraction with encoding type versioning. + */ +export class CodeShareManager { + /** @type {string} */ + static CURRENT_ENCODING = 'gzip-b64'; + + /** + * Available encoding types for future extensibility. + * @type {Object} + */ + static ENCODERS = { + 'gzip-b64': { + compress: CodeCompression.compress.bind(CodeCompression), + decompress: CodeCompression.decompress.bind(CodeCompression) + } + }; + + /** + * Generates a shareable URL with compressed code and encoding type. + * @param {Object} code - Code object containing swift and dts properties + * @param {string} code.swift - Swift code + * @param {string} code.dts - TypeScript definition code + * @returns {Promise} Shareable URL + */ + static async generateShareUrl(code) { + const codeData = JSON.stringify(code); + const encoder = this.ENCODERS[this.CURRENT_ENCODING]; + + if (!encoder) { + throw new Error(`Unsupported encoding type: ${this.CURRENT_ENCODING}`); + } + + const compressed = await encoder.compress(codeData); + + const url = new URL(window.location.href); + url.searchParams.set('code', compressed); + url.searchParams.set('enc', this.CURRENT_ENCODING); + + return url.toString(); + } + + /** + * Extracts code from URL parameters with encoding type detection. + * @param {string} [url] - URL to extract from (defaults to current URL) + * @returns {Promise} Code object or null if no code found + */ + static async extractCodeFromUrl(url) { + const urlObj = new URL(url || window.location.href); + const compressedCode = urlObj.searchParams.get('code'); + const encodingType = urlObj.searchParams.get('enc') || this.CURRENT_ENCODING; + + if (!compressedCode) { + return null; + } + + const encoder = this.ENCODERS[encodingType]; + if (!encoder) { + console.error(`Unsupported encoding type: ${encodingType}`); + throw new Error(`Unsupported encoding type: ${encodingType}. Supported types: ${Object.keys(this.ENCODERS).join(', ')}`); + } + + try { + const decompressed = await encoder.decompress(compressedCode); + return JSON.parse(decompressed); + } catch (error) { + console.error('Failed to extract code from URL:', error); + throw new Error(`Failed to decode shared code (encoding: ${encodingType}): ${error.message}`); + } + } + + /** + * Checks if current URL contains shared code. + * @returns {boolean} True if URL contains code parameter + */ + static hasSharedCode() { + return new URL(window.location.href).searchParams.has('code'); + } +} \ No newline at end of file diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/editor.js b/Examples/PlayBridgeJS/Sources/JavaScript/editor.js index 88a07c43..dabe7dc5 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/editor.js +++ b/Examples/PlayBridgeJS/Sources/JavaScript/editor.js @@ -1,4 +1,12 @@ +// @ts-check + +/** + * The editor system for the BridgeJS Playground. + */ export class EditorSystem { + /** + * Creates a new instance of the EditorSystem. + */ constructor() { this.editors = new Map(); this.config = { @@ -70,7 +78,9 @@ export class EditorSystem { async loadMonaco() { return new Promise((resolve) => { + // @ts-ignore require.config({ paths: { vs: 'https://unpkg.com/monaco-editor@0.45.0/min/vs' } }); + // @ts-ignore require(['vs/editor/editor.main'], resolve); }); } @@ -98,12 +108,15 @@ export class EditorSystem { return; } + // @ts-ignore const model = monaco.editor.createModel( config.placeholder, config.language, + // @ts-ignore monaco.Uri.parse(config.modelUri) ); + // @ts-ignore const editor = monaco.editor.create(element, { ...commonOptions, value: config.placeholder, @@ -140,7 +153,6 @@ export class EditorSystem { } this.updateTabStates(); - this.updateLayout(); } updateTabStates() { @@ -183,6 +195,15 @@ export class EditorSystem { }; } + /** + * Sets the inputs for the editor system. + * @param {{swift: string, dts: string}} sampleCode - The sample code to set the inputs to. + */ + setInputs({ swift, dts }) { + this.editors.get('swift')?.setValue(swift); + this.editors.get('dts')?.setValue(dts); + } + updateOutputs(result) { const outputMap = { 'import-glue': () => result.importSwiftGlue(), @@ -200,36 +221,6 @@ export class EditorSystem { }); } - loadSampleCode() { - const sampleSwift = `import JavaScriptKit - -@JS public func calculateTotal(price: Double, quantity: Int) -> Double { - return price * Double(quantity) -} - -@JS class ShoppingCart { - private var items: [(name: String, price: Double, quantity: Int)] = [] - - @JS init() {} - - @JS public func addItem(name: String, price: Double, quantity: Int) { - items.append((name, price, quantity)) - } - - @JS public func getTotal() -> Double { - return items.reduce(0) { $0 + $1.price * Double($1.quantity) } - } -}`; - - const sampleDts = `export type Console = { - log: (message: string) => void; -} -export function console(): Console;`; - - this.editors.get('swift')?.setValue(sampleSwift); - this.editors.get('dts')?.setValue(sampleDts); - } - addChangeListeners(callback) { this.config.input.forEach(config => { const editor = this.editors.get(config.key); diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/index.js b/Examples/PlayBridgeJS/Sources/JavaScript/index.js index 8983c5eb..356f9b4f 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/index.js +++ b/Examples/PlayBridgeJS/Sources/JavaScript/index.js @@ -3,11 +3,39 @@ import { BridgeJSPlayground } from './app.js'; Error.stackTraceLimit = Infinity; +const SWIFT_INPUT = `import JavaScriptKit + +@JS public func calculateTotal(price: Double, quantity: Int) -> Double { + return price * Double(quantity) +} + +@JS class ShoppingCart { + private var items: [(name: String, price: Double, quantity: Int)] = [] + + @JS init() {} + + @JS public func addItem(name: String, price: Double, quantity: Int) { + items.append((name, price, quantity)) + } + + @JS public func getTotal() -> Double { + return items.reduce(0) { $0 + $1.price * Double($1.quantity) } + } +}` + +const DTS_INPUT = `export type Console = { + log(message: string): void; +} +export function console(): Console;` + // Initialize the playground when the page loads document.addEventListener('DOMContentLoaded', async () => { try { const playground = new BridgeJSPlayground(); - await playground.initialize(); + await playground.initialize({ + swift: SWIFT_INPUT, + dts: DTS_INPUT + }); } catch (error) { console.error('Failed to initialize playground:', error); } diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/styles.css b/Examples/PlayBridgeJS/Sources/JavaScript/styles.css index a41258c2..da62834e 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/styles.css +++ b/Examples/PlayBridgeJS/Sources/JavaScript/styles.css @@ -83,6 +83,116 @@ body { font-weight: 400; } +.share-controls { + margin-top: 16px; + display: flex; + justify-content: center; + position: relative; +} + +.share-button { + padding: 10px 20px; + border: none; + background-color: var(--color-button-background); + color: var(--color-button-text); + border-radius: 8px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: background-color 0.2s ease; +} + +.share-button:hover { + background-color: var(--color-button-background-hover); +} + +.share-dialog { + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + z-index: 1000; + margin-top: 8px; + min-width: 400px; + max-width: 90vw; +} + +.share-dialog.hidden { + display: none; +} + +.share-dialog-content { + background-color: var(--color-fill); + border: 1px solid var(--color-border); + border-radius: 12px; + padding: 20px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); +} + +.share-dialog-content h3 { + margin: 0 0 16px 0; + font-size: 18px; + font-weight: 600; + text-align: center; +} + +.share-url-container { + display: flex; + gap: 8px; + margin-bottom: 16px; +} + +.share-url-input { + flex: 1; + padding: 10px 12px; + border: 1px solid var(--color-border); + border-radius: 6px; + font-size: 14px; + font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace; + background-color: var(--color-fill-secondary); + color: var(--color-text); +} + +.copy-button { + padding: 10px 16px; + border: none; + background-color: var(--color-figure-green); + color: var(--color-fill); + border-radius: 6px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: opacity 0.2s ease; +} + +.copy-button:hover { + opacity: 0.8; +} + +.copy-button.copied { + background-color: var(--color-figure-blue); +} + +.share-dialog-actions { + text-align: center; +} + +.close-button { + padding: 8px 16px; + border: 1px solid var(--color-border); + background-color: var(--color-fill); + color: var(--color-text); + border-radius: 6px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: background-color 0.2s ease; +} + +.close-button:hover { + background-color: var(--color-fill-secondary); +} + .error-display { margin-bottom: 24px; padding: 16px; @@ -111,6 +221,47 @@ body { word-break: break-word; } +/* Progress bar */ +.progress { + margin: 16px auto 0 auto; + max-width: 640px; + opacity: 0; + visibility: hidden; + transition: opacity 0.2s ease; +} + +.progress.show { + opacity: 1; + visibility: visible; +} + +.progress.hidden { + opacity: 0; + visibility: hidden; +} + +.progress-track { + height: 8px; + background-color: var(--color-fill-tertiary); + border: 1px solid var(--color-border); + border-radius: 999px; + overflow: hidden; +} + +.progress-fill { + height: 100%; + width: 0%; + background-color: var(--color-figure-blue); + transition: width 0.2s ease; +} + +.progress-label { + margin-top: 8px; + text-align: center; + font-size: 12px; + color: var(--color-secondary-label); +} + .main-content { flex: 1; display: grid; @@ -266,4 +417,24 @@ body { .section-header h2 { font-size: 18px; } + + .share-dialog { + min-width: 320px; + max-width: calc(100vw - 24px); + left: 50%; + transform: translateX(-50%); + } + + .share-dialog-content { + padding: 16px; + } + + .share-url-container { + flex-direction: column; + gap: 12px; + } + + .share-url-input { + font-size: 12px; + } } \ No newline at end of file diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/BridgeJSUtilities b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/BridgeJSUtilities new file mode 120000 index 00000000..a43f43e2 --- /dev/null +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/BridgeJSUtilities @@ -0,0 +1 @@ +../../../../Plugins/BridgeJS/Sources/BridgeJSUtilities \ No newline at end of file diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift index b0656df9..9e4515f5 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift @@ -11,7 +11,7 @@ public func _bjs_PlayBridgeJS_init() -> UnsafeMutableRawPointer { #if arch(wasm32) let ret = PlayBridgeJS() - return Unmanaged.passRetained(ret).toOpaque() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -19,19 +19,11 @@ public func _bjs_PlayBridgeJS_init() -> UnsafeMutableRawPointer { @_expose(wasm, "bjs_PlayBridgeJS_update") @_cdecl("bjs_PlayBridgeJS_update") -public func _bjs_PlayBridgeJS_update(_self: UnsafeMutableRawPointer, swiftSourceBytes: Int32, swiftSourceLen: Int32, dtsSourceBytes: Int32, dtsSourceLen: Int32) -> UnsafeMutableRawPointer { +public func _bjs_PlayBridgeJS_update(_self: UnsafeMutableRawPointer, swiftSourceBytes: Int32, swiftSourceLength: Int32, dtsSourceBytes: Int32, dtsSourceLength: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) do { - let swiftSource = String(unsafeUninitializedCapacity: Int(swiftSourceLen)) { b in - _swift_js_init_memory(swiftSourceBytes, b.baseAddress.unsafelyUnwrapped) - return Int(swiftSourceLen) - } - let dtsSource = String(unsafeUninitializedCapacity: Int(dtsSourceLen)) { b in - _swift_js_init_memory(dtsSourceBytes, b.baseAddress.unsafelyUnwrapped) - return Int(dtsSourceLen) - } - let ret = try Unmanaged.fromOpaque(_self).takeUnretainedValue().update(swiftSource: swiftSource, dtsSource: dtsSource) - return Unmanaged.passRetained(ret).toOpaque() + let ret = try PlayBridgeJS.bridgeJSLiftParameter(_self).update(swiftSource: String.bridgeJSLiftParameter(swiftSourceBytes, swiftSourceLength), dtsSource: String.bridgeJSLiftParameter(dtsSourceBytes, dtsSourceLength)) + return ret.bridgeJSLowerReturn() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -56,14 +48,26 @@ public func _bjs_PlayBridgeJS_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } +extension PlayBridgeJS: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "PlayBridgeJS", name: "bjs_PlayBridgeJS_wrap") + func _bjs_PlayBridgeJS_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_PlayBridgeJS_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_PlayBridgeJS_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + @_expose(wasm, "bjs_PlayBridgeJSOutput_outputJs") @_cdecl("bjs_PlayBridgeJSOutput_outputJs") public func _bjs_PlayBridgeJSOutput_outputJs(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().outputJs() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = PlayBridgeJSOutput.bridgeJSLiftParameter(_self).outputJs() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -73,10 +77,8 @@ public func _bjs_PlayBridgeJSOutput_outputJs(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PlayBridgeJSOutput_outputDts") public func _bjs_PlayBridgeJSOutput_outputDts(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().outputDts() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = PlayBridgeJSOutput.bridgeJSLiftParameter(_self).outputDts() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -86,10 +88,8 @@ public func _bjs_PlayBridgeJSOutput_outputDts(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PlayBridgeJSOutput_importSwiftGlue") public func _bjs_PlayBridgeJSOutput_importSwiftGlue(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().importSwiftGlue() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = PlayBridgeJSOutput.bridgeJSLiftParameter(_self).importSwiftGlue() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -99,10 +99,8 @@ public func _bjs_PlayBridgeJSOutput_importSwiftGlue(_self: UnsafeMutableRawPoint @_cdecl("bjs_PlayBridgeJSOutput_exportSwiftGlue") public func _bjs_PlayBridgeJSOutput_exportSwiftGlue(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().exportSwiftGlue() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = PlayBridgeJSOutput.bridgeJSLiftParameter(_self).exportSwiftGlue() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -112,4 +110,18 @@ public func _bjs_PlayBridgeJSOutput_exportSwiftGlue(_self: UnsafeMutableRawPoint @_cdecl("bjs_PlayBridgeJSOutput_deinit") public func _bjs_PlayBridgeJSOutput_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() +} + +extension PlayBridgeJSOutput: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "PlayBridgeJS", name: "bjs_PlayBridgeJSOutput_wrap") + func _bjs_PlayBridgeJSOutput_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_PlayBridgeJSOutput_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_PlayBridgeJSOutput_wrap(Unmanaged.passRetained(self).toOpaque())))) + } } \ No newline at end of file diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift index 5d82db08..4d35ef74 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift @@ -19,18 +19,14 @@ func createTS2Skeleton() throws(JSException) -> TS2Skeleton { if let error = _swift_js_take_exception() { throw error } - return TS2Skeleton(takingThis: ret) + return TS2Skeleton.bridgeJSLiftReturn(ret) } -struct TS2Skeleton { - let this: JSObject +struct TS2Skeleton: _JSBridgedClass { + let jsObject: JSObject - init(this: JSObject) { - self.this = this - } - - init(takingThis this: Int32) { - self.this = JSObject(id: UInt32(bitPattern: this)) + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject } func convert(_ ts: String) throws(JSException) -> String { @@ -42,18 +38,11 @@ struct TS2Skeleton { fatalError("Only available on WebAssembly") } #endif - var ts = ts - let tsId = ts.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - let ret = bjs_TS2Skeleton_convert(Int32(bitPattern: self.this.id), tsId) + let ret = bjs_TS2Skeleton_convert(self.bridgeJSLowerParameter(), ts.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } } \ No newline at end of file diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json index c4d55d27..625556f2 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -46,7 +46,11 @@ } } ], - "name" : "PlayBridgeJS" + "name" : "PlayBridgeJS", + "properties" : [ + + ], + "swiftCallName" : "PlayBridgeJS" }, { "methods" : [ @@ -115,10 +119,18 @@ } } ], - "name" : "PlayBridgeJSOutput" + "name" : "PlayBridgeJSOutput", + "properties" : [ + + ], + "swiftCallName" : "PlayBridgeJSOutput" } + ], + "enums" : [ + ], "functions" : [ - ] + ], + "moduleName" : "PlayBridgeJS" } \ No newline at end of file diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/bridge-js.config.json b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/bridge-js.config.json index e69de29b..0967ef42 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/bridge-js.config.json +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/bridge-js.config.json @@ -0,0 +1 @@ +{} diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift index 8f224994..35d2340d 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift @@ -17,7 +17,7 @@ import class Foundation.JSONDecoder } func _update(swiftSource: String, dtsSource: String) throws -> PlayBridgeJSOutput { - let exportSwift = ExportSwift(progress: .silent) + let exportSwift = ExportSwift(progress: .silent, moduleName: "Playground") let sourceFile = Parser.parse(source: swiftSource) try exportSwift.addSourceFile(sourceFile, "Playground.swift") let exportResult = try exportSwift.finalize() diff --git a/Examples/PlayBridgeJS/build.sh b/Examples/PlayBridgeJS/build.sh index c4dc8d71..a5900e49 100755 --- a/Examples/PlayBridgeJS/build.sh +++ b/Examples/PlayBridgeJS/build.sh @@ -1,3 +1,5 @@ #!/bin/bash set -euxo pipefail -env JAVASCRIPTKIT_EXPERIMENTAL_BRIDGEJS=1 swift package --swift-sdk "${SWIFT_SDK_ID_wasm32_unknown_wasi:-${SWIFT_SDK_ID:-wasm32-unknown-wasi}}" -c "${1:-debug}" js --use-cdn +env JAVASCRIPTKIT_EXPERIMENTAL_BRIDGEJS=1 swift package --swift-sdk "${SWIFT_SDK_ID_wasm32_unknown_wasi:-${SWIFT_SDK_ID:-wasm32-unknown-wasi}}" -c "${1:-debug}" \ + plugin --allow-writing-to-package-directory \ + js --use-cdn --output ./Bundle diff --git a/Examples/PlayBridgeJS/index.html b/Examples/PlayBridgeJS/index.html index 21566ee0..799c36a1 100644 --- a/Examples/PlayBridgeJS/index.html +++ b/Examples/PlayBridgeJS/index.html @@ -9,7 +9,8 @@ @@ -26,6 +27,27 @@

BridgeJS Playground

Interactive playground to preview bridged code generated by BridgeJS

+ +
diff --git a/Examples/PlayBridgeJS/package.json b/Examples/PlayBridgeJS/package.json new file mode 100644 index 00000000..2feb6565 --- /dev/null +++ b/Examples/PlayBridgeJS/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "@typescript/vfs": "^1.6.1" + } +} diff --git a/Package.swift b/Package.swift index 435ae1a1..cf3055c3 100644 --- a/Package.swift +++ b/Package.swift @@ -28,7 +28,7 @@ let package = Package( .plugin(name: "BridgeJSCommandPlugin", targets: ["BridgeJSCommandPlugin"]), ], dependencies: [ - .package(url: "https://github.com/swiftlang/swift-syntax", "600.0.0"..<"601.0.0") + .package(url: "https://github.com/swiftlang/swift-syntax", "600.0.0"..<"602.0.0") ], targets: [ .target( @@ -151,12 +151,11 @@ let package = Package( .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), ], - path: "Plugins/BridgeJS/Sources/BridgeJSTool", - exclude: ["TS2Skeleton/JavaScript"] + exclude: ["TS2Skeleton/JavaScript", "README.md"] ), .testTarget( name: "BridgeJSRuntimeTests", - dependencies: ["JavaScriptKit"], + dependencies: ["JavaScriptKit", "JavaScriptEventLoop"], exclude: [ "bridge-js.config.json", "bridge-js.d.ts", diff --git a/Plugins/BridgeJS/Package.swift b/Plugins/BridgeJS/Package.swift index f7241d86..bb7daac2 100644 --- a/Plugins/BridgeJS/Package.swift +++ b/Plugins/BridgeJS/Package.swift @@ -2,13 +2,6 @@ import PackageDescription -let coreDependencies: [Target.Dependency] = [ - .product(name: "SwiftParser", package: "swift-syntax"), - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftBasicFormat", package: "swift-syntax"), - .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), -] - let package = Package( name: "BridgeJS", platforms: [.macOS(.v13)], @@ -19,11 +12,48 @@ let package = Package( .target(name: "BridgeJSBuildPlugin"), .executableTarget( name: "BridgeJSTool", - dependencies: coreDependencies + dependencies: [ + "BridgeJSCore", + "TS2Skeleton", + ] + ), + .target( + name: "TS2Skeleton", + dependencies: [ + "BridgeJSCore", + "BridgeJSSkeleton", + ], + exclude: ["JavaScript"] + ), + .target( + name: "BridgeJSCore", + dependencies: [ + "BridgeJSSkeleton", + "BridgeJSUtilities", + .product(name: "SwiftParser", package: "swift-syntax"), + .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftBasicFormat", package: "swift-syntax"), + .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), + ] ), + .target(name: "BridgeJSSkeleton"), + .target(name: "BridgeJSUtilities"), + + .target( + name: "BridgeJSLink", + dependencies: [ + "BridgeJSSkeleton", + "BridgeJSUtilities", + ] + ), + .testTarget( name: "BridgeJSToolTests", - dependencies: coreDependencies, + dependencies: [ + "BridgeJSCore", + "BridgeJSLink", + "TS2Skeleton", + ], exclude: ["__Snapshots__", "Inputs"] ), ] diff --git a/Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift b/Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift index 8353b5c4..9ea09520 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift @@ -42,6 +42,8 @@ struct BridgeJSBuildPlugin: BuildToolPlugin { executable: try context.tool(named: "BridgeJSTool").url, arguments: [ "export", + "--module-name", + target.name, "--output-skeleton", outputSkeletonPath.path, "--output-swift", @@ -77,6 +79,8 @@ struct BridgeJSBuildPlugin: BuildToolPlugin { executable: try context.tool(named: "BridgeJSTool").url, arguments: [ "import", + "--target-dir", + target.directoryURL.path, "--output-skeleton", outputSkeletonPath.path, "--output-swift", diff --git a/Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift b/Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift index 88222a0e..a4a2fcf1 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift @@ -105,6 +105,8 @@ extension BridgeJSCommandPlugin.Context { try runBridgeJSTool( arguments: [ "export", + "--module-name", + target.name, "--output-skeleton", generatedJavaScriptDirectory.appending(path: "BridgeJS.ExportSwift.json").path, "--output-swift", @@ -125,6 +127,8 @@ extension BridgeJSCommandPlugin.Context { try runBridgeJSTool( arguments: [ "import", + "--target-dir", + target.directoryURL.path, "--output-skeleton", generatedJavaScriptDirectory.appending(path: "BridgeJS.ImportTS.json").path, "--output-swift", diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSConfig.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSConfig.swift new file mode 100644 index 00000000..cf6f881e --- /dev/null +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSConfig.swift @@ -0,0 +1,55 @@ +import struct Foundation.URL +import struct Foundation.Data +import class Foundation.FileManager +import class Foundation.JSONDecoder + +/// Configuration file representation for BridgeJS. +public struct BridgeJSConfig: Codable { + /// A mapping of tool names to their override paths. + /// + /// If not present, the tool will be searched for in the system PATH. + public var tools: [String: String]? + + /// Load the configuration file from the SwiftPM package target directory. + /// + /// Files are loaded **in this order** and merged (later files override earlier ones): + /// 1. `bridge-js.config.json` + /// 2. `bridge-js.config.local.json` + public static func load(targetDirectory: URL) throws -> BridgeJSConfig { + // Define file paths in priority order: base first, then local overrides + let files = [ + targetDirectory.appendingPathComponent("bridge-js.config.json"), + targetDirectory.appendingPathComponent("bridge-js.config.local.json"), + ] + + var config = BridgeJSConfig() + + for file in files { + do { + if let loaded = try loadConfig(from: file) { + config = config.merging(overrides: loaded) + } + } catch { + throw BridgeJSCoreError("Failed to parse \(file.path): \(error)") + } + } + + return config + } + + /// Load a config file from the given URL if it exists, otherwise return nil + private static func loadConfig(from url: URL) throws -> BridgeJSConfig? { + guard FileManager.default.fileExists(atPath: url.path) else { + return nil + } + let data = try Data(contentsOf: url) + return try JSONDecoder().decode(BridgeJSConfig.self, from: data) + } + + /// Merge the current configuration with the overrides. + func merging(overrides: BridgeJSConfig) -> BridgeJSConfig { + return BridgeJSConfig( + tools: (tools ?? [:]).merging(overrides.tools ?? [:], uniquingKeysWith: { $1 }) + ) + } +} diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSCoreError.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSCoreError.swift index 6e313754..9cbec438 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSCoreError.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSCoreError.swift @@ -1,7 +1,7 @@ -struct BridgeJSCoreError: Swift.Error, CustomStringConvertible { - let description: String +public struct BridgeJSCoreError: Swift.Error, CustomStringConvertible { + public let description: String - init(_ message: String) { + public init(_ message: String) { self.description = message } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index dfe161e9..117158df 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -1,6 +1,12 @@ import SwiftBasicFormat import SwiftSyntax import SwiftSyntaxBuilder +#if canImport(BridgeJSSkeleton) +import BridgeJSSkeleton +#endif +#if canImport(BridgeJSUtilities) +import BridgeJSUtilities +#endif /// Exports Swift functions and classes to JavaScript /// @@ -11,15 +17,18 @@ import SwiftSyntaxBuilder /// /// The generated skeletons will be used by ``BridgeJSLink`` to generate /// JavaScript glue code and TypeScript definitions. -class ExportSwift { +public class ExportSwift { let progress: ProgressReporting + let moduleName: String private var exportedFunctions: [ExportedFunction] = [] private var exportedClasses: [ExportedClass] = [] + private var exportedEnums: [ExportedEnum] = [] private var typeDeclResolver: TypeDeclResolver = TypeDeclResolver() - init(progress: ProgressReporting) { + public init(progress: ProgressReporting, moduleName: String) { self.progress = progress + self.moduleName = moduleName } /// Processes a Swift source file to find declarations marked with @JS @@ -27,7 +36,7 @@ class ExportSwift { /// - Parameters: /// - sourceFile: The parsed Swift source file to process /// - inputFilePath: The file path for error reporting - func addSourceFile(_ sourceFile: SourceFileSyntax, _ inputFilePath: String) throws { + public func addSourceFile(_ sourceFile: SourceFileSyntax, _ inputFilePath: String) throws { progress.print("Processing \(inputFilePath)") typeDeclResolver.addSourceFile(sourceFile) @@ -44,13 +53,18 @@ class ExportSwift { /// /// - Returns: A tuple containing the generated Swift code and a skeleton /// describing the exported APIs - func finalize() throws -> (outputSwift: String, outputSkeleton: ExportedSkeleton)? { - guard let outputSwift = renderSwiftGlue() else { + public func finalize() throws -> (outputSwift: String, outputSkeleton: ExportedSkeleton)? { + guard let outputSwift = try renderSwiftGlue() else { return nil } return ( outputSwift: outputSwift, - outputSkeleton: ExportedSkeleton(functions: exportedFunctions, classes: exportedClasses) + outputSkeleton: ExportedSkeleton( + moduleName: moduleName, + functions: exportedFunctions, + classes: exportedClasses, + enums: exportedEnums + ) ) } @@ -59,11 +73,32 @@ class ExportSwift { /// The names of the exported classes, in the order they were written in the source file var exportedClassNames: [String] = [] var exportedClassByName: [String: ExportedClass] = [:] + /// The names of the exported enums, in the order they were written in the source file + var exportedEnumNames: [String] = [] + var exportedEnumByName: [String: ExportedEnum] = [:] var errors: [DiagnosticError] = [] + /// Creates a unique key for a class by combining name and namespace + private func classKey(name: String, namespace: [String]?) -> String { + if let namespace = namespace, !namespace.isEmpty { + return "\(namespace.joined(separator: ".")).\(name)" + } else { + return name + } + } + + /// Temporary storage for enum data during visitor traversal since EnumCaseDeclSyntax lacks parent context + struct CurrentEnum { + var name: String? + var cases: [EnumCase] = [] + var rawType: String? + } + var currentEnum = CurrentEnum() + enum State { case topLevel - case classBody(name: String) + case classBody(name: String, key: String) + case enumBody(name: String) } struct StateStack { @@ -110,15 +145,22 @@ class ExportSwift { override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { switch state { case .topLevel: - if let exportedFunction = visitFunction(node: node) { + if let exportedFunction = visitFunction( + node: node + ) { exportedFunctions.append(exportedFunction) } return .skipChildren - case .classBody(let name): - if let exportedFunction = visitFunction(node: node) { - exportedClassByName[name]?.methods.append(exportedFunction) + case .classBody(_, let classKey): + if let exportedFunction = visitFunction( + node: node + ) { + exportedClassByName[classKey]?.methods.append(exportedFunction) } return .skipChildren + case .enumBody: + diagnose(node: node, message: "Functions are not supported inside enums") + return .skipChildren } } @@ -163,8 +205,14 @@ class ExportSwift { switch state { case .topLevel: abiName = "bjs_\(name)" - case .classBody(let className): + case .classBody(let className, _): abiName = "bjs_\(className)_\(name)" + case .enumBody: + abiName = "" + diagnose( + node: node, + message: "Functions are not supported inside enums" + ) } guard let effects = collectEffects(signature: node.signature) else { @@ -222,10 +270,32 @@ class ExportSwift { return namespaceString.split(separator: ".").map(String.init) } + private func extractEnumStyle( + from jsAttribute: AttributeSyntax + ) -> EnumEmitStyle? { + guard let arguments = jsAttribute.arguments?.as(LabeledExprListSyntax.self), + let styleArg = arguments.first(where: { $0.label?.text == "enumStyle" }) + else { + return nil + } + let text = styleArg.expression.trimmedDescription + if text.contains("tsEnum") { + return .tsEnum + } + if text.contains("const") { + return .const + } + return nil + } + override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { guard node.attributes.hasJSAttribute() else { return .skipChildren } - guard case .classBody(let name) = state else { - diagnose(node: node, message: "@JS init must be inside a @JS class") + guard case .classBody(let className, _) = state else { + if case .enumBody(_) = state { + diagnose(node: node, message: "Initializers are not supported inside enums") + } else { + diagnose(node: node, message: "@JS init must be inside a @JS class") + } return .skipChildren } @@ -255,34 +325,325 @@ class ExportSwift { } let constructor = ExportedConstructor( - abiName: "bjs_\(name)_init", + abiName: "bjs_\(className)_init", parameters: parameters, effects: effects ) - exportedClassByName[name]?.constructor = constructor + if case .classBody(_, let classKey) = state { + exportedClassByName[classKey]?.constructor = constructor + } + return .skipChildren + } + + override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { + guard node.attributes.hasJSAttribute() else { return .skipChildren } + guard case .classBody(_, let classKey) = state else { + diagnose(node: node, message: "@JS var must be inside a @JS class") + return .skipChildren + } + + if let jsAttribute = node.attributes.firstJSAttribute, + extractNamespace(from: jsAttribute) != nil + { + diagnose( + node: jsAttribute, + message: "Namespace is not supported for property declarations", + hint: "Remove the namespace from @JS attribute" + ) + } + + // Process each binding (variable declaration) + for binding in node.bindings { + guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else { + diagnose(node: binding.pattern, message: "Complex patterns not supported for @JS properties") + continue + } + + let propertyName = pattern.identifier.text + + guard let typeAnnotation = binding.typeAnnotation else { + diagnose(node: binding, message: "@JS property must have explicit type annotation") + continue + } + + guard let propertyType = self.parent.lookupType(for: typeAnnotation.type) else { + diagnoseUnsupportedType(node: typeAnnotation.type, type: typeAnnotation.type.trimmedDescription) + continue + } + + // Check if property is readonly + let isLet = node.bindingSpecifier.tokenKind == .keyword(.let) + let isGetterOnly = node.bindings.contains(where: { + switch $0.accessorBlock?.accessors { + case .accessors(let accessors): + // Has accessors - check if it only has a getter (no setter, willSet, or didSet) + return !accessors.contains(where: { accessor in + let tokenKind = accessor.accessorSpecifier.tokenKind + return tokenKind == .keyword(.set) || tokenKind == .keyword(.willSet) + || tokenKind == .keyword(.didSet) + }) + case .getter: + // Has only a getter block + return true + case nil: + // No accessor block - this is a stored property, not readonly + return false + } + }) + let isReadonly = isLet || isGetterOnly + + let exportedProperty = ExportedProperty( + name: propertyName, + type: propertyType, + isReadonly: isReadonly + ) + + exportedClassByName[classKey]?.properties.append(exportedProperty) + } + return .skipChildren } override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { let name = node.name.text - stateStack.push(state: .classBody(name: name)) + guard let jsAttribute = node.attributes.firstJSAttribute else { + return .skipChildren + } - guard let jsAttribute = node.attributes.firstJSAttribute else { return .skipChildren } + let attributeNamespace = extractNamespace(from: jsAttribute) + let computedNamespace = computeNamespace(for: node) - let namespace = extractNamespace(from: jsAttribute) - exportedClassByName[name] = ExportedClass( + if computedNamespace != nil && attributeNamespace != nil { + diagnose( + node: jsAttribute, + message: "Nested classes cannot specify their own namespace", + hint: "Remove the namespace from @JS attribute - nested classes inherit namespace from parent" + ) + return .skipChildren + } + + let effectiveNamespace = computedNamespace ?? attributeNamespace + + let swiftCallName = ExportSwift.computeSwiftCallName(for: node, itemName: name) + let explicitAccessControl = computeExplicitAtLeastInternalAccessControl( + for: node, + message: "Class visibility must be at least internal" + ) + let exportedClass = ExportedClass( name: name, + swiftCallName: swiftCallName, + explicitAccessControl: explicitAccessControl, constructor: nil, methods: [], - namespace: namespace + properties: [], + namespace: effectiveNamespace ) - exportedClassNames.append(name) + let uniqueKey = classKey(name: name, namespace: effectiveNamespace) + + stateStack.push(state: .classBody(name: name, key: uniqueKey)) + exportedClassByName[uniqueKey] = exportedClass + exportedClassNames.append(uniqueKey) return .visitChildren } + override func visitPost(_ node: ClassDeclSyntax) { + // Make sure we pop the state stack only if we're in a class body state (meaning we successfully pushed) + if case .classBody(_, _) = stateStack.current { + stateStack.pop() + } + } + + override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { + guard node.attributes.hasJSAttribute() else { + return .skipChildren + } + + guard let jsAttribute = node.attributes.firstJSAttribute else { + return .skipChildren + } + + let name = node.name.text + + let rawType: String? = node.inheritanceClause?.inheritedTypes.first { inheritedType in + let typeName = inheritedType.type.trimmedDescription + return Constants.supportedRawTypes.contains(typeName) + }?.type.trimmedDescription + + let attributeNamespace = extractNamespace(from: jsAttribute) + let computedNamespace = computeNamespace(for: node) + + if computedNamespace != nil && attributeNamespace != nil { + diagnose( + node: jsAttribute, + message: "Nested enums cannot specify their own namespace", + hint: "Remove the namespace from @JS attribute - nested enums inherit namespace from parent" + ) + return .skipChildren + } + + currentEnum.name = name + currentEnum.cases = [] + currentEnum.rawType = rawType + + stateStack.push(state: .enumBody(name: name)) + + return .visitChildren + } + + override func visitPost(_ node: EnumDeclSyntax) { + guard let jsAttribute = node.attributes.firstJSAttribute, + let enumName = currentEnum.name + else { + // Only pop if we have a valid enum that was processed + if case .enumBody(_) = stateStack.current { + stateStack.pop() + } + return + } + + let attributeNamespace = extractNamespace(from: jsAttribute) + let computedNamespace = computeNamespace(for: node) + + let effectiveNamespace: [String]? + if computedNamespace == nil && attributeNamespace != nil { + effectiveNamespace = attributeNamespace + } else { + effectiveNamespace = computedNamespace + } + + let emitStyle = extractEnumStyle(from: jsAttribute) ?? .const + if case .tsEnum = emitStyle, + let raw = currentEnum.rawType, + let rawEnum = SwiftEnumRawType.from(raw), rawEnum == .bool + { + diagnose( + node: jsAttribute, + message: "TypeScript enum style is not supported for Bool raw-value enums", + hint: "Use enumStyle: .const or change the raw type to String or a numeric type" + ) + } + + let swiftCallName = ExportSwift.computeSwiftCallName(for: node, itemName: enumName) + let explicitAccessControl = computeExplicitAtLeastInternalAccessControl( + for: node, + message: "Enum visibility must be at least internal" + ) + let exportedEnum = ExportedEnum( + name: enumName, + swiftCallName: swiftCallName, + explicitAccessControl: explicitAccessControl, + cases: currentEnum.cases, + rawType: currentEnum.rawType, + namespace: effectiveNamespace, + emitStyle: emitStyle + ) + exportedEnumByName[enumName] = exportedEnum + exportedEnumNames.append(enumName) + + currentEnum = CurrentEnum() stateStack.pop() } + + override func visit(_ node: EnumCaseDeclSyntax) -> SyntaxVisitorContinueKind { + for element in node.elements { + let caseName = element.name.text + let rawValue: String? + var associatedValues: [AssociatedValue] = [] + + if currentEnum.rawType != nil { + if let stringLiteral = element.rawValue?.value.as(StringLiteralExprSyntax.self) { + rawValue = stringLiteral.segments.first?.as(StringSegmentSyntax.self)?.content.text + } else if let boolLiteral = element.rawValue?.value.as(BooleanLiteralExprSyntax.self) { + rawValue = boolLiteral.literal.text + } else if let intLiteral = element.rawValue?.value.as(IntegerLiteralExprSyntax.self) { + rawValue = intLiteral.literal.text + } else if let floatLiteral = element.rawValue?.value.as(FloatLiteralExprSyntax.self) { + rawValue = floatLiteral.literal.text + } else { + rawValue = nil + } + } else { + rawValue = nil + } + if let parameterClause = element.parameterClause { + for param in parameterClause.parameters { + guard let bridgeType = parent.lookupType(for: param.type) else { + diagnose( + node: param.type, + message: "Unsupported associated value type: \(param.type.trimmedDescription)", + hint: "Only primitive types and types defined in the same module are allowed" + ) + continue + } + + let label = param.firstName?.text + associatedValues.append(AssociatedValue(label: label, type: bridgeType)) + } + } + let enumCase = EnumCase( + name: caseName, + rawValue: rawValue, + associatedValues: associatedValues + ) + + currentEnum.cases.append(enumCase) + } + + return .visitChildren + } + + /// Computes namespace by walking up the AST hierarchy to find parent namespace enums + /// If parent enum is a namespace enum (no cases) then it will be used as part of namespace for given node + /// + /// + /// Method allows for explicit namespace for top level enum, it will be used as base namespace and will concat enum name + private func computeNamespace(for node: some SyntaxProtocol) -> [String]? { + var namespace: [String] = [] + var currentNode: Syntax? = node.parent + + while let parent = currentNode { + if let enumDecl = parent.as(EnumDeclSyntax.self), + enumDecl.attributes.hasJSAttribute() + { + let isNamespaceEnum = !enumDecl.memberBlock.members.contains { member in + member.decl.is(EnumCaseDeclSyntax.self) + } + if isNamespaceEnum { + namespace.insert(enumDecl.name.text, at: 0) + + if let jsAttribute = enumDecl.attributes.firstJSAttribute, + let explicitNamespace = extractNamespace(from: jsAttribute) + { + namespace = explicitNamespace + namespace + break + } + } + } + currentNode = parent.parent + } + + return namespace.isEmpty ? nil : namespace + } + + /// Requires the node to have at least internal access control. + private func computeExplicitAtLeastInternalAccessControl( + for node: some WithModifiersSyntax, + message: String + ) -> String? { + guard let accessControl = node.explicitAccessControl else { + return nil + } + guard accessControl.isAtLeastInternal else { + diagnose( + node: accessControl, + message: message, + hint: "Use `internal`, `package` or `public` access control" + ) + return nil + } + return accessControl.name.text + } } func parseSingleFile(_ sourceFile: SourceFileSyntax) throws -> [DiagnosticError] { @@ -294,23 +655,84 @@ class ExportSwift { collector.exportedClassByName[$0]! } ) + exportedEnums.append( + contentsOf: collector.exportedEnumNames.map { + collector.exportedEnumByName[$0]! + } + ) return collector.errors } + /// Computes the full Swift call name by walking up the AST hierarchy to find all parent enums + /// This generates the qualified name needed for Swift code generation (e.g., "Networking.API.HTTPServer") + private static func computeSwiftCallName(for node: some SyntaxProtocol, itemName: String) -> String { + var swiftPath: [String] = [] + var currentNode: Syntax? = node.parent + + while let parent = currentNode { + if let enumDecl = parent.as(EnumDeclSyntax.self), + enumDecl.attributes.hasJSAttribute() + { + swiftPath.insert(enumDecl.name.text, at: 0) + } + currentNode = parent.parent + } + + if swiftPath.isEmpty { + return itemName + } else { + return swiftPath.joined(separator: ".") + "." + itemName + } + } + func lookupType(for type: TypeSyntax) -> BridgeType? { if let primitive = BridgeType(swiftType: type.trimmedDescription) { return primitive } - guard let identifier = type.as(IdentifierTypeSyntax.self) else { + + guard let typeDecl = typeDeclResolver.resolve(type) else { return nil } - guard let typeDecl = typeDeclResolver.lookupType(for: identifier) else { - return nil + + if let enumDecl = typeDecl.as(EnumDeclSyntax.self) { + let swiftCallName = ExportSwift.computeSwiftCallName(for: enumDecl, itemName: enumDecl.name.text) + let rawTypeString = enumDecl.inheritanceClause?.inheritedTypes.first { inheritedType in + let typeName = inheritedType.type.trimmedDescription + return Constants.supportedRawTypes.contains(typeName) + }?.type.trimmedDescription + + if let rawTypeString, let rawType = SwiftEnumRawType.from(rawTypeString) { + return .rawValueEnum(swiftCallName, rawType) + } else { + let hasAnyCases = enumDecl.memberBlock.members.contains { member in + member.decl.is(EnumCaseDeclSyntax.self) + } + if !hasAnyCases { + return .namespaceEnum(swiftCallName) + } + let hasAssociatedValues = + enumDecl.memberBlock.members.contains { member in + guard let caseDecl = member.decl.as(EnumCaseDeclSyntax.self) else { return false } + return caseDecl.elements.contains { element in + if let params = element.parameterClause?.parameters { + return !params.isEmpty + } + return false + } + } + if hasAssociatedValues { + return .associatedValueEnum(swiftCallName) + } else { + return .caseEnum(swiftCallName) + } + } } + guard typeDecl.is(ClassDeclSyntax.self) || typeDecl.is(ActorDeclSyntax.self) else { return nil } - return .swiftHeapObject(typeDecl.name.text) + let swiftCallName = ExportSwift.computeSwiftCallName(for: typeDecl, itemName: typeDecl.name.text) + return .swiftHeapObject(swiftCallName) } static let prelude: DeclSyntax = """ @@ -323,25 +745,74 @@ class ExportSwift { @_spi(BridgeJS) import JavaScriptKit """ - func renderSwiftGlue() -> String? { + func renderSwiftGlue() throws -> String? { var decls: [DeclSyntax] = [] - guard exportedFunctions.count > 0 || exportedClasses.count > 0 else { + guard exportedFunctions.count > 0 || exportedClasses.count > 0 || exportedEnums.count > 0 else { return nil } decls.append(Self.prelude) + + for enumDef in exportedEnums { + if enumDef.enumType == .simple { + decls.append(renderCaseEnumHelpers(enumDef)) + } else { + decls.append("extension \(raw: enumDef.swiftCallName): _BridgedSwiftEnumNoPayload {}") + } + } + for function in exportedFunctions { - decls.append(renderSingleExportedFunction(function: function)) + decls.append(try renderSingleExportedFunction(function: function)) } for klass in exportedClasses { - decls.append(contentsOf: renderSingleExportedClass(klass: klass)) + decls.append(contentsOf: try renderSingleExportedClass(klass: klass)) } let format = BasicFormat() return decls.map { $0.formatted(using: format).description }.joined(separator: "\n\n") } + func renderCaseEnumHelpers(_ enumDef: ExportedEnum) -> DeclSyntax { + let typeName = enumDef.swiftCallName + var initCases: [String] = [] + var valueCases: [String] = [] + for (index, c) in enumDef.cases.enumerated() { + initCases.append("case \(index): self = .\(c.name)") + valueCases.append("case .\(c.name): return \(index)") + } + let initSwitch = (["switch bridgeJSRawValue {"] + initCases + ["default: return nil", "}"]).joined( + separator: "\n" + ) + let valueSwitch = (["switch self {"] + valueCases + ["}"]).joined(separator: "\n") + + return """ + extension \(raw: typeName) { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> \(raw: typeName) { + return \(raw: typeName)(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> \(raw: typeName) { + return \(raw: typeName)(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { + \(raw: initSwitch) + } + + private var bridgeJSRawValue: Int32 { + \(raw: valueSwitch) + } + } + """ + } + class ExportedThunkBuilder { var body: [CodeBlockItemSyntax] = [] - var abiParameterForwardings: [LabeledExprSyntax] = [] + var liftedParameterExprs: [ExprSyntax] = [] + var parameters: [Parameter] = [] var abiParameterSignatures: [(name: String, type: WasmCoreType)] = [] var abiReturnType: WasmCoreType? let effects: Effects @@ -359,92 +830,41 @@ class ExportSwift { self.body.append(item) } - func liftParameter(param: Parameter) { - switch param.type { - case .bool: - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name) == 1") - ) - ) - abiParameterSignatures.append((param.name, .i32)) - case .int: - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.type.swiftType)(\(raw: param.name))") - ) - ) - abiParameterSignatures.append((param.name, .i32)) - case .float: - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name)") - ) - ) - abiParameterSignatures.append((param.name, .f32)) - case .double: - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name)") - ) - ) - abiParameterSignatures.append((param.name, .f64)) - case .string: - let bytesLabel = "\(param.name)Bytes" - let lengthLabel = "\(param.name)Len" - let prepare: CodeBlockItemSyntax = """ - let \(raw: param.name) = String(unsafeUninitializedCapacity: Int(\(raw: lengthLabel))) { b in - _swift_js_init_memory(\(raw: bytesLabel), b.baseAddress.unsafelyUnwrapped) - return Int(\(raw: lengthLabel)) - } - """ - append(prepare) - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name)") - ) - ) - abiParameterSignatures.append((bytesLabel, .i32)) - abiParameterSignatures.append((lengthLabel, .i32)) - case .jsObject(nil): - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - 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)) - case .swiftHeapObject: - // UnsafeMutableRawPointer is passed as an i32 pointer - let objectExpr: ExprSyntax = - "Unmanaged<\(raw: param.type.swiftType)>.fromOpaque(\(raw: param.name)).takeUnretainedValue()" - abiParameterForwardings.append( - LabeledExprSyntax(label: param.label, expression: objectExpr) + func liftParameter(param: Parameter) throws { + parameters.append(param) + let liftingInfo = try param.type.liftParameterInfo() + let argumentsToLift: [String] + if liftingInfo.parameters.count == 1 { + argumentsToLift = [param.name] + } else { + argumentsToLift = liftingInfo.parameters.map { (name, _) in param.name + name.capitalizedFirstLetter } + } + liftedParameterExprs.append( + ExprSyntax( + "\(raw: param.type.swiftType).bridgeJSLiftParameter(\(raw: argumentsToLift.joined(separator: ", ")))" ) - abiParameterSignatures.append((param.name, .pointer)) - case .void: - break + ) + for (name, type) in zip(argumentsToLift, liftingInfo.parameters.map { $0.type }) { + abiParameterSignatures.append((name, type)) } } + private func removeFirstLiftedParameter() -> (parameter: Parameter, expr: ExprSyntax) { + let parameter = parameters.removeFirst() + let expr = liftedParameterExprs.removeFirst() + return (parameter, expr) + } + private func renderCallStatement(callee: ExprSyntax, returnType: BridgeType) -> CodeBlockItemSyntax { + let labeledParams = zip(parameters, liftedParameterExprs).map { param, expr in + LabeledExprSyntax(label: param.label, expression: expr) + } var callExpr: ExprSyntax = - "\(raw: callee)(\(raw: abiParameterForwardings.map { $0.description }.joined(separator: ", ")))" + "\(raw: callee)(\(raw: labeledParams.map { $0.description }.joined(separator: ", ")))" if effects.isAsync { - callExpr = ExprSyntax(AwaitExprSyntax(awaitKeyword: .keyword(.await), expression: callExpr)) + callExpr = ExprSyntax( + AwaitExprSyntax(awaitKeyword: .keyword(.await).with(\.trailingTrivia, .space), expression: callExpr) + ) } if effects.isThrows { callExpr = ExprSyntax( @@ -454,11 +874,15 @@ class ExportSwift { ) ) } - let retMutability = returnType == .string ? "var" : "let" + + if effects.isAsync, returnType != .void { + return CodeBlockItemSyntax(item: .init(StmtSyntax("return \(raw: callExpr).jsValue"))) + } + if returnType == .void { return CodeBlockItemSyntax(item: .init(ExpressionStmtSyntax(expression: callExpr))) } else { - return CodeBlockItemSyntax(item: .init(DeclSyntax("\(raw: retMutability) ret = \(raw: callExpr)"))) + return CodeBlockItemSyntax(item: .init(DeclSyntax("let ret = \(raw: callExpr)"))) } } @@ -468,57 +892,63 @@ class ExportSwift { } func callMethod(klassName: String, methodName: String, returnType: BridgeType) { - let _selfParam = self.abiParameterForwardings.removeFirst() + let (_, selfExpr) = removeFirstLiftedParameter() let item = renderCallStatement( - callee: "\(raw: _selfParam).\(raw: methodName)", + callee: "\(raw: selfExpr).\(raw: methodName)", returnType: returnType ) append(item) } - func lowerReturnValue(returnType: BridgeType) { - abiReturnType = returnType.abiReturnType + func callPropertyGetter(klassName: String, propertyName: String, returnType: BridgeType) { + let (_, selfExpr) = removeFirstLiftedParameter() + if returnType == .void { + append("\(raw: selfExpr).\(raw: propertyName)") + } else { + append("let ret = \(raw: selfExpr).\(raw: propertyName)") + } + } + + func callPropertySetter(klassName: String, propertyName: String) { + let (_, selfExpr) = removeFirstLiftedParameter() + let (_, newValueExpr) = removeFirstLiftedParameter() + append("\(raw: selfExpr).\(raw: propertyName) = \(raw: newValueExpr)") + } - switch returnType { - case .void: break - case .int, .float, .double: - append("return \(raw: abiReturnType!.swiftType)(ret)") - case .bool: - append("return Int32(ret ? 1 : 0)") - case .string: - append( - """ - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } - """ - ) - case .jsObject(nil): - append( - """ - return _swift_js_retain(Int32(bitPattern: ret.id)) - """ - ) - case .jsObject(_?): - append( - """ - return _swift_js_retain(Int32(bitPattern: ret.this.id)) - """ - ) - case .swiftHeapObject: - // Perform a manual retain on the object, which will be balanced by a - // release called via FinalizationRegistry - append( - """ - return Unmanaged.passRetained(ret).toOpaque() - """ - ) + func lowerReturnValue(returnType: BridgeType) throws { + if effects.isAsync { + // Async functions always return a Promise, which is a JSObject + try _lowerReturnValue(returnType: .jsObject(nil)) + } else { + try _lowerReturnValue(returnType: returnType) + } + } + + private func _lowerReturnValue(returnType: BridgeType) throws { + let loweringInfo = try returnType.loweringReturnInfo() + abiReturnType = loweringInfo.returnType + if returnType == .void { + return } + if effects.isAsync { + // The return value of async function (T of `(...) async -> T`) is + // handled by the JSPromise.async, so we don't need to do anything here. + return + } + + append("return ret.bridgeJSLowerReturn()") } func render(abiName: String) -> DeclSyntax { let body: CodeBlockItemListSyntax - if effects.isThrows { + if effects.isAsync { + body = """ + let ret = JSPromise.async { + \(CodeBlockItemListSyntax(self.body)) + }.jsObject + return ret.bridgeJSLowerReturn() + """ + } else if effects.isThrows { body = """ do { \(CodeBlockItemListSyntax(self.body)) @@ -576,13 +1006,13 @@ class ExportSwift { } } - func renderSingleExportedFunction(function: ExportedFunction) -> DeclSyntax { + func renderSingleExportedFunction(function: ExportedFunction) throws -> DeclSyntax { let builder = ExportedThunkBuilder(effects: function.effects) for param in function.parameters { - builder.liftParameter(param: param) + try builder.liftParameter(param: param) } builder.call(name: function.name, returnType: function.returnType) - builder.lowerReturnValue(returnType: function.returnType) + try builder.lowerReturnValue(returnType: function.returnType) return builder.render(abiName: function.abiName) } @@ -633,48 +1063,127 @@ class ExportSwift { /// Unmanaged.fromOpaque(pointer).release() /// } /// ``` - func renderSingleExportedClass(klass: ExportedClass) -> [DeclSyntax] { + func renderSingleExportedClass(klass: ExportedClass) throws -> [DeclSyntax] { var decls: [DeclSyntax] = [] + if let constructor = klass.constructor { let builder = ExportedThunkBuilder(effects: constructor.effects) for param in constructor.parameters { - builder.liftParameter(param: param) + try builder.liftParameter(param: param) } - builder.call(name: klass.name, returnType: .swiftHeapObject(klass.name)) - builder.lowerReturnValue(returnType: .swiftHeapObject(klass.name)) + builder.call(name: klass.swiftCallName, returnType: BridgeType.swiftHeapObject(klass.name)) + try builder.lowerReturnValue(returnType: BridgeType.swiftHeapObject(klass.name)) decls.append(builder.render(abiName: constructor.abiName)) } for method in klass.methods { let builder = ExportedThunkBuilder(effects: method.effects) - builder.liftParameter( - param: Parameter(label: nil, name: "_self", type: .swiftHeapObject(klass.name)) + try builder.liftParameter( + param: Parameter(label: nil, name: "_self", type: BridgeType.swiftHeapObject(klass.swiftCallName)) ) for param in method.parameters { - builder.liftParameter(param: param) + try builder.liftParameter(param: param) } builder.callMethod( - klassName: klass.name, + klassName: klass.swiftCallName, methodName: method.name, returnType: method.returnType ) - builder.lowerReturnValue(returnType: method.returnType) + try builder.lowerReturnValue(returnType: method.returnType) decls.append(builder.render(abiName: method.abiName)) } + // Generate property getters and setters + for property in klass.properties { + // Generate getter + let getterBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false)) + try getterBuilder.liftParameter( + param: Parameter(label: nil, name: "_self", type: .swiftHeapObject(klass.name)) + ) + getterBuilder.callPropertyGetter( + klassName: klass.name, + propertyName: property.name, + returnType: property.type + ) + try getterBuilder.lowerReturnValue(returnType: property.type) + decls.append(getterBuilder.render(abiName: property.getterAbiName(className: klass.name))) + + // Generate setter if property is not readonly + if !property.isReadonly { + let setterBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false)) + try setterBuilder.liftParameter( + param: Parameter(label: nil, name: "_self", type: .swiftHeapObject(klass.name)) + ) + try setterBuilder.liftParameter( + param: Parameter(label: "value", name: "value", type: property.type) + ) + setterBuilder.callPropertySetter( + klassName: klass.name, + propertyName: property.name + ) + try setterBuilder.lowerReturnValue(returnType: .void) + decls.append(setterBuilder.render(abiName: property.setterAbiName(className: klass.name))) + } + } + do { decls.append( """ @_expose(wasm, "bjs_\(raw: klass.name)_deinit") @_cdecl("bjs_\(raw: klass.name)_deinit") public func _bjs_\(raw: klass.name)_deinit(pointer: UnsafeMutableRawPointer) { - Unmanaged<\(raw: klass.name)>.fromOpaque(pointer).release() + Unmanaged<\(raw: klass.swiftCallName)>.fromOpaque(pointer).release() } """ ) } + // Generate ConvertibleToJSValue extension + decls.append(renderConvertibleToJSValueExtension(klass: klass)) + return decls } + + /// Generates a ConvertibleToJSValue extension for the exported class + /// + /// # Example + /// + /// For a class named `Greeter`, this generates: + /// + /// ```swift + /// extension Greeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + /// var jsValue: JSValue { + /// @_extern(wasm, module: "MyModule", name: "bjs_Greeter_wrap") + /// func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + /// return JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque()))) + /// } + /// } + /// ``` + func renderConvertibleToJSValueExtension(klass: ExportedClass) -> DeclSyntax { + let wrapFunctionName = "_bjs_\(klass.name)_wrap" + let externFunctionName = "bjs_\(klass.name)_wrap" + + // If the class has an explicit access control, we need to add it to the extension declaration. + let accessControl = klass.explicitAccessControl.map { "\($0) " } ?? "" + return """ + extension \(raw: klass.swiftCallName): ConvertibleToJSValue, _BridgedSwiftHeapObject { + \(raw: accessControl)var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "\(raw: moduleName)", name: "\(raw: externFunctionName)") + func \(raw: wrapFunctionName)(_: UnsafeMutableRawPointer) -> Int32 + #else + func \(raw: wrapFunctionName)(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: \(raw: wrapFunctionName)(Unmanaged.passRetained(self).toOpaque())))) + } + } + """ + } +} + +fileprivate enum Constants { + static let supportedRawTypes = SwiftEnumRawType.allCases.map { $0.rawValue } } extension AttributeListSyntax { @@ -736,6 +1245,149 @@ extension BridgeType { case .jsObject(let name?): return name case .swiftHeapObject(let name): return name case .void: return "Void" + case .caseEnum(let name): return name + case .rawValueEnum(let name, _): return name + case .associatedValueEnum(let name): return name + case .namespaceEnum(let name): return name + } + } + + struct LiftingIntrinsicInfo: Sendable { + let parameters: [(name: String, type: WasmCoreType)] + + static let bool = LiftingIntrinsicInfo(parameters: [("value", .i32)]) + static let int = LiftingIntrinsicInfo(parameters: [("value", .i32)]) + static let float = LiftingIntrinsicInfo(parameters: [("value", .f32)]) + static let double = LiftingIntrinsicInfo(parameters: [("value", .f64)]) + static let string = LiftingIntrinsicInfo(parameters: [("bytes", .i32), ("length", .i32)]) + static let jsObject = LiftingIntrinsicInfo(parameters: [("value", .i32)]) + static let swiftHeapObject = LiftingIntrinsicInfo(parameters: [("value", .pointer)]) + static let void = LiftingIntrinsicInfo(parameters: []) + static let caseEnum = LiftingIntrinsicInfo(parameters: [("value", .i32)]) + } + + func liftParameterInfo() throws -> LiftingIntrinsicInfo { + switch self { + case .bool: return .bool + case .int: return .int + case .float: return .float + case .double: return .double + case .string: return .string + case .jsObject: return .jsObject + case .swiftHeapObject: return .swiftHeapObject + case .void: return .void + case .caseEnum: return .caseEnum + case .rawValueEnum(_, let rawType): + switch rawType { + case .bool: return .bool + case .int: return .int + case .float: return .float + case .double: return .double + case .string: return .string + case .int32: return .int + case .int64: return .int + case .uint: return .int + case .uint32: return .int + case .uint64: return .int + } + case .associatedValueEnum: + throw BridgeJSCoreError("Associated value enums are not supported to pass as parameters") + case .namespaceEnum: + throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters") + } + } + + struct LoweringIntrinsicInfo: Sendable { + let returnType: WasmCoreType? + + static let bool = LoweringIntrinsicInfo(returnType: .i32) + static let int = LoweringIntrinsicInfo(returnType: .i32) + static let float = LoweringIntrinsicInfo(returnType: .f32) + static let double = LoweringIntrinsicInfo(returnType: .f64) + static let string = LoweringIntrinsicInfo(returnType: nil) + static let jsObject = LoweringIntrinsicInfo(returnType: .i32) + static let swiftHeapObject = LoweringIntrinsicInfo(returnType: .pointer) + static let void = LoweringIntrinsicInfo(returnType: nil) + static let caseEnum = LoweringIntrinsicInfo(returnType: .i32) + static let rawValueEnum = LoweringIntrinsicInfo(returnType: .i32) + } + + func loweringReturnInfo() throws -> LoweringIntrinsicInfo { + switch self { + case .bool: return .bool + case .int: return .int + case .float: return .float + case .double: return .double + case .string: return .string + case .jsObject: return .jsObject + case .swiftHeapObject: return .swiftHeapObject + case .void: return .void + case .caseEnum: return .caseEnum + case .rawValueEnum(_, let rawType): + switch rawType { + case .bool: return .bool + case .int: return .int + case .float: return .float + case .double: return .double + case .string: return .string + case .int32: return .int + case .int64: return .int + case .uint: return .int + case .uint32: return .int + case .uint64: return .int + } + case .associatedValueEnum: + throw BridgeJSCoreError("Associated value enums are not supported to pass as parameters") + case .namespaceEnum: + throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters") + } + } +} + +extension DeclModifierSyntax { + var isAccessControl: Bool { + switch self.name.tokenKind { + case .keyword(.private), + .keyword(.fileprivate), + .keyword(.internal), + .keyword(.package), + .keyword(.public), + .keyword(.open): + return true + default: + return false + } + } + + var isAtLeastInternal: Bool { + switch self.name.tokenKind { + case .keyword(.private): false + case .keyword(.fileprivate): false + case .keyword(.internal): true + case .keyword(.package): true + case .keyword(.public): true + case .keyword(.open): true + default: false + } + } + + var isAtLeastPackage: Bool { + switch self.name.tokenKind { + case .keyword(.private): false + case .keyword(.fileprivate): false + case .keyword(.internal): true + case .keyword(.package): true + case .keyword(.public): true + case .keyword(.open): true + default: false + } + } +} + +extension WithModifiersSyntax { + var explicitAccessControl: DeclModifierSyntax? { + return self.modifiers.first { modifier in + modifier.isAccessControl } } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index 37181114..9fc8a62d 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -1,6 +1,12 @@ import SwiftBasicFormat import SwiftSyntax import SwiftSyntaxBuilder +#if canImport(BridgeJSSkeleton) +import BridgeJSSkeleton +#endif +#if canImport(BridgeJSUtilities) +import BridgeJSUtilities +#endif /// Imports TypeScript declarations and generates Swift bridge code /// @@ -10,25 +16,25 @@ import SwiftSyntaxBuilder /// /// The generated skeletons will be used by ``BridgeJSLink`` to generate /// JavaScript glue code and TypeScript definitions. -struct ImportTS { - let progress: ProgressReporting - private(set) var skeleton: ImportedModuleSkeleton +public struct ImportTS { + public let progress: ProgressReporting + public private(set) var skeleton: ImportedModuleSkeleton private var moduleName: String { skeleton.moduleName } - init(progress: ProgressReporting, moduleName: String) { + public init(progress: ProgressReporting, moduleName: String) { self.progress = progress self.skeleton = ImportedModuleSkeleton(moduleName: moduleName, children: []) } /// Adds a skeleton to the importer's state - mutating func addSkeleton(_ skeleton: ImportedFileSkeleton) { + public mutating func addSkeleton(_ skeleton: ImportedFileSkeleton) { self.skeleton.children.append(skeleton) } /// Finalizes the import process and generates Swift code - func finalize() throws -> String? { + public func finalize() throws -> String? { var decls: [DeclSyntax] = [] for skeleton in self.skeleton.children { for function in skeleton.functions { @@ -65,82 +71,19 @@ struct ImportTS { } func lowerParameter(param: Parameter) throws { - switch param.type { - case .bool: - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("Int32(\(raw: param.name) ? 1 : 0)") - ) - ) - abiParameterSignatures.append((param.name, .i32)) - case .int: - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name)") - ) - ) - abiParameterSignatures.append((param.name, .i32)) - case .float: - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name)") - ) - ) - abiParameterSignatures.append((param.name, .f32)) - case .double: - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name)") - ) - ) - abiParameterSignatures.append((param.name, .f64)) - case .string: - let stringIdName = "\(param.name)Id" - body.append( - """ - var \(raw: param.name) = \(raw: param.name) - - """ - ) - body.append( - """ - let \(raw: stringIdName) = \(raw: param.name).withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - """ - ) - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: stringIdName)") - ) - ) - abiParameterSignatures.append((param.name, .i32)) - case .jsObject(_?): - abiParameterSignatures.append((param.name, .i32)) - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("Int32(bitPattern: \(raw: param.name).this.id)") - ) - ) - case .jsObject(nil): - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("Int32(bitPattern: \(raw: param.name).id)") - ) + let loweringInfo = try param.type.loweringParameterInfo() + assert( + loweringInfo.loweredParameters.count == 1, + "For now, we require a single parameter to be lowered to a single Wasm core type" + ) + let (_, type) = loweringInfo.loweredParameters[0] + abiParameterForwardings.append( + LabeledExprSyntax( + label: param.label, + expression: ExprSyntax("\(raw: param.name).bridgeJSLowerParameter()") ) - abiParameterSignatures.append((param.name, .i32)) - case .swiftHeapObject(_): - throw BridgeJSCoreError("swiftHeapObject is not supported in imported signatures") - case .void: - break - } + ) + abiParameterSignatures.append((param.name, type)) } func call(returnType: BridgeType) { @@ -155,41 +98,12 @@ struct ImportTS { } func liftReturnValue(returnType: BridgeType) throws { - switch returnType { - case .bool: - abiReturnType = .i32 - body.append("return ret == 1") - case .int: - abiReturnType = .i32 - body.append("return \(raw: returnType.swiftType)(ret)") - case .float: - abiReturnType = .f32 - body.append("return \(raw: returnType.swiftType)(ret)") - case .double: - abiReturnType = .f64 - body.append("return \(raw: returnType.swiftType)(ret)") - case .string: - abiReturnType = .i32 - body.append( - """ - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } - """ - ) - case .jsObject(let name): - abiReturnType = .i32 - if let name = name { - body.append("return \(raw: name)(takingThis: ret)") - } else { - body.append("return JSObject(id: UInt32(bitPattern: ret))") - } - case .swiftHeapObject(_): - throw BridgeJSCoreError("swiftHeapObject is not supported in imported signatures") - case .void: - break + let liftingInfo = try returnType.liftingReturnInfo() + abiReturnType = liftingInfo.valueToLift + if returnType == .void { + return } + body.append("return \(raw: returnType.swiftType).bridgeJSLiftReturn(ret)") } func assignThis(returnType: BridgeType) { @@ -197,7 +111,7 @@ struct ImportTS { preconditionFailure("assignThis can only be called with a jsObject return type") } abiReturnType = .i32 - body.append("self.this = JSObject(id: UInt32(bitPattern: ret))") + body.append("self.jsObject = JSObject(id: UInt32(bitPattern: ret))") } func renderImportDecl() -> DeclSyntax { @@ -242,7 +156,7 @@ struct ImportTS { func renderThunkDecl(name: String, parameters: [Parameter], returnType: BridgeType) -> DeclSyntax { return DeclSyntax( FunctionDeclSyntax( - name: .identifier(name), + name: .identifier(name.backtickIfNeeded()), signature: FunctionSignatureSyntax( parameterClause: FunctionParameterClauseSyntax(parametersBuilder: { for param in parameters { @@ -385,7 +299,7 @@ struct ImportTS { try builder.lowerParameter(param: newValue) builder.call(returnType: .void) return builder.renderThunkDecl( - name: "set\(property.name.capitalizedFirstLetter())", + name: "set\(property.name.capitalizedFirstLetter)", parameters: [newValue], returnType: .void ) @@ -401,7 +315,9 @@ struct ImportTS { bindingsBuilder: { PatternBindingListSyntax { PatternBindingSyntax( - pattern: IdentifierPatternSyntax(identifier: .identifier(property.name)), + pattern: IdentifierPatternSyntax( + identifier: .identifier(property.name.backtickIfNeeded()) + ), typeAnnotation: TypeAnnotationSyntax( type: IdentifierTypeSyntax(name: .identifier(property.type.swiftType)) ), @@ -424,25 +340,22 @@ struct ImportTS { let classDecl = try StructDeclSyntax( leadingTrivia: Self.renderDocumentation(documentation: type.documentation), name: .identifier(name), + inheritanceClause: InheritanceClauseSyntax( + inheritedTypesBuilder: { + InheritedTypeSyntax(type: TypeSyntax("_JSBridgedClass")) + } + ), memberBlockBuilder: { DeclSyntax( """ - let this: JSObject - """ - ).with(\.trailingTrivia, .newlines(2)) - - DeclSyntax( - """ - init(this: JSObject) { - self.this = this - } + let jsObject: JSObject """ ).with(\.trailingTrivia, .newlines(2)) DeclSyntax( """ - init(takingThis this: Int32) { - self.this = JSObject(id: UInt32(bitPattern: this)) + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject } """ ).with(\.trailingTrivia, .newlines(2)) @@ -498,9 +411,66 @@ struct ImportTS { } } +extension BridgeType { + struct LoweringParameterInfo { + let loweredParameters: [(name: String, type: WasmCoreType)] + + static let bool = LoweringParameterInfo(loweredParameters: [("value", .i32)]) + static let int = LoweringParameterInfo(loweredParameters: [("value", .i32)]) + static let float = LoweringParameterInfo(loweredParameters: [("value", .f32)]) + static let double = LoweringParameterInfo(loweredParameters: [("value", .f64)]) + static let string = LoweringParameterInfo(loweredParameters: [("value", .i32)]) + static let jsObject = LoweringParameterInfo(loweredParameters: [("value", .i32)]) + static let void = LoweringParameterInfo(loweredParameters: []) + } + + func loweringParameterInfo() throws -> LoweringParameterInfo { + switch self { + case .bool: return .bool + case .int: return .int + case .float: return .float + case .double: return .double + case .string: return .string + case .jsObject: return .jsObject + case .void: return .void + case .swiftHeapObject: + throw BridgeJSCoreError("swiftHeapObject is not supported in imported signatures") + case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum: + throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports") + } + } + + struct LiftingReturnInfo { + let valueToLift: WasmCoreType? + + static let bool = LiftingReturnInfo(valueToLift: .i32) + static let int = LiftingReturnInfo(valueToLift: .i32) + static let float = LiftingReturnInfo(valueToLift: .f32) + static let double = LiftingReturnInfo(valueToLift: .f64) + static let string = LiftingReturnInfo(valueToLift: .i32) + static let jsObject = LiftingReturnInfo(valueToLift: .i32) + static let void = LiftingReturnInfo(valueToLift: nil) + } + + func liftingReturnInfo() throws -> LiftingReturnInfo { + switch self { + case .bool: return .bool + case .int: return .int + case .float: return .float + case .double: return .double + case .string: return .string + case .jsObject: return .jsObject + case .void: return .void + case .swiftHeapObject: + throw BridgeJSCoreError("swiftHeapObject is not supported in imported signatures") + case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum: + throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports") + } + } +} + extension String { - func capitalizedFirstLetter() -> String { - guard !isEmpty else { return self } - return prefix(1).uppercased() + dropFirst() + func backtickIfNeeded() -> String { + return self.isValidSwiftIdentifier(for: .variableName) ? self : "`\(self)`" } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ProgressReporting.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ProgressReporting.swift index 4e92a198..d1a2aa6d 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ProgressReporting.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ProgressReporting.swift @@ -1,7 +1,7 @@ -struct ProgressReporting { +public struct ProgressReporting { let print: (String) -> Void - init(verbose: Bool) { + public init(verbose: Bool) { self.init(print: verbose ? { Swift.print($0) } : { _ in }) } @@ -9,11 +9,11 @@ struct ProgressReporting { self.print = print } - static var silent: ProgressReporting { + public static var silent: ProgressReporting { return ProgressReporting(print: { _ in }) } - func print(_ message: String) { + public func print(_ message: String) { self.print(message) } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift index a7b183af..25200e13 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift @@ -109,4 +109,37 @@ class TypeDeclResolver { func lookupType(fullyQualified: QualifiedName) -> TypeDecl? { return typeDeclByQualifiedName[fullyQualified] } + + /// Resolves a type usage node to the corresponding nominal type declaration collected in this resolver. + /// + /// Supported inputs: + /// - IdentifierTypeSyntax (e.g. `Method`) — resolved relative to the lexical scope, preferring the innermost enclosing type. + /// - MemberTypeSyntax (e.g. `Networking.API.Method`) — resolved by recursively building the fully qualified name. + /// + /// Resolution strategy: + /// 1. If the node is IdentifierTypeSyntax, call `lookupType(for:)` which attempts scope-aware qualification via `tryQualify`. + /// 2. Otherwise, attempt to build a fully qualified name with `qualifiedComponents(from:)` and look it up with `lookupType(fullyQualified:)`. + /// + /// - Parameter type: The SwiftSyntax node representing a type appearance in source code. + /// - Returns: The nominal declaration (enum/class/actor/struct) if found, otherwise nil. + func resolve(_ type: TypeSyntax) -> TypeDecl? { + if let id = type.as(IdentifierTypeSyntax.self) { + return lookupType(for: id) + } + if let components = qualifiedComponents(from: type) { + return lookupType(fullyQualified: components) + } + return nil + } + + private func qualifiedComponents(from type: TypeSyntax) -> QualifiedName? { + if let m = type.as(MemberTypeSyntax.self) { + guard let base = qualifiedComponents(from: TypeSyntax(m.baseType)) else { return nil } + return base + [m.name.text] + } else if let id = type.as(IdentifierTypeSyntax.self) { + return [id.name.text] + } else { + return nil + } + } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 022c5cbb..360a6274 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -1,5 +1,11 @@ import class Foundation.JSONDecoder import struct Foundation.Data +#if canImport(BridgeJSSkeleton) +import BridgeJSSkeleton +#endif +#if canImport(BridgeJSUtilities) +import BridgeJSUtilities +#endif struct BridgeJSLink { /// The exported skeletons @@ -40,14 +46,16 @@ struct BridgeJSLink { let swiftHeapObjectClassJs = """ /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - constructor(pointer, deinit) { - this.pointer = pointer; - this.hasReleased = false; - this.deinit = deinit; - this.registry = new FinalizationRegistry((pointer) => { + static __wrap(pointer, deinit, prototype) { + const obj = Object.create(prototype); + obj.pointer = pointer; + obj.hasReleased = false; + obj.deinit = deinit; + obj.registry = new FinalizationRegistry((pointer) => { deinit(pointer); }); - this.registry.register(this, this.pointer); + obj.registry.register(this, obj.pointer); + return obj; } release() { @@ -64,6 +72,11 @@ struct BridgeJSLink { var dtsClassLines: [String] = [] var namespacedFunctions: [ExportedFunction] = [] var namespacedClasses: [ExportedClass] = [] + var namespacedEnums: [ExportedEnum] = [] + var enumConstantLines: [String] = [] + var dtsEnumLines: [String] = [] + var topLevelEnumLines: [String] = [] + var topLevelDtsEnumLines: [String] = [] if exportedSkeletons.contains(where: { $0.classes.count > 0 }) { classLines.append( @@ -80,7 +93,7 @@ struct BridgeJSLink { for skeleton in exportedSkeletons { for klass in skeleton.classes { - let (jsType, dtsType, dtsExportEntry) = renderExportedClass(klass) + let (jsType, dtsType, dtsExportEntry) = try renderExportedClass(klass) classLines.append(contentsOf: jsType) exportsLines.append("\(klass.name),") dtsExportLines.append(contentsOf: dtsExportEntry) @@ -91,8 +104,37 @@ struct BridgeJSLink { } } + if !skeleton.enums.isEmpty { + for enumDefinition in skeleton.enums { + let (jsEnum, dtsEnum) = try renderExportedEnum(enumDefinition) + + switch enumDefinition.enumType { + case .namespace: + break + case .simple, .rawValue: + var exportedJsEnum = jsEnum + if !exportedJsEnum.isEmpty && exportedJsEnum[0].hasPrefix("const ") { + exportedJsEnum[0] = "export " + exportedJsEnum[0] + } + topLevelEnumLines.append(contentsOf: exportedJsEnum) + topLevelDtsEnumLines.append(contentsOf: dtsEnum) + + if enumDefinition.namespace != nil { + namespacedEnums.append(enumDefinition) + } + case .associatedValue: + enumConstantLines.append(contentsOf: jsEnum) + exportsLines.append("\(enumDefinition.name),") + if enumDefinition.namespace != nil { + namespacedEnums.append(enumDefinition) + } + dtsEnumLines.append(contentsOf: dtsEnum) + } + } + } + for function in skeleton.functions { - var (js, dts) = renderExportedFunction(function: function) + var (js, dts) = try renderExportedFunction(function: function) if function.namespace != nil { namespacedFunctions.append(function) @@ -119,36 +161,63 @@ struct BridgeJSLink { importObjectBuilders.append(importObjectBuilder) } - let hasNamespacedItems = !namespacedFunctions.isEmpty || !namespacedClasses.isEmpty + let hasNamespacedItems = !namespacedFunctions.isEmpty || !namespacedClasses.isEmpty || !namespacedEnums.isEmpty + + let namespaceBuilder = NamespaceBuilder() + let namespaceDeclarationsLines = namespaceBuilder.namespaceDeclarations( + exportedSkeletons: exportedSkeletons, + renderTSSignatureCallback: { parameters, returnType, effects in + self.renderTSSignature(parameters: parameters, returnType: returnType, effects: effects) + } + ) let exportsSection: String if hasNamespacedItems { - let namespaceSetupCode = renderGlobalNamespace( + let namespacedEnumsForExports = namespacedEnums.filter { $0.enumType == .associatedValue } + let namespaceSetupCode = namespaceBuilder.renderGlobalNamespace( namespacedFunctions: namespacedFunctions, - namespacedClasses: namespacedClasses + namespacedClasses: namespacedClasses, + namespacedEnums: namespacedEnumsForExports ) .map { $0.indent(count: 12) }.joined(separator: "\n") + + let enumSection = + enumConstantLines.isEmpty + ? "" : enumConstantLines.map { $0.indent(count: 12) }.joined(separator: "\n") + "\n" + exportsSection = """ \(classLines.map { $0.indent(count: 12) }.joined(separator: "\n")) - const exports = { + \(enumSection)\("const exports = {".indent(count: 12)) \(exportsLines.map { $0.indent(count: 16) }.joined(separator: "\n")) - }; + \("};".indent(count: 12)) \(namespaceSetupCode) - return exports; + \("return exports;".indent(count: 12)) }, """ } else { + let enumSection = + enumConstantLines.isEmpty + ? "" : enumConstantLines.map { $0.indent(count: 12) }.joined(separator: "\n") + "\n" + exportsSection = """ \(classLines.map { $0.indent(count: 12) }.joined(separator: "\n")) - return { + \(enumSection)\("return {".indent(count: 12)) \(exportsLines.map { $0.indent(count: 16) }.joined(separator: "\n")) - }; + \("};".indent(count: 12)) }, """ } + let topLevelEnumsSection = topLevelEnumLines.isEmpty ? "" : topLevelEnumLines.joined(separator: "\n") + "\n\n" + + let topLevelNamespaceCode = namespaceBuilder.renderTopLevelEnumNamespaceAssignments( + namespacedEnums: namespacedEnums + ) + let namespaceAssignmentsSection = + topLevelNamespaceCode.isEmpty ? "" : topLevelNamespaceCode.joined(separator: "\n") + "\n\n" + let outputJs = """ // NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, // DO NOT EDIT. @@ -156,74 +225,83 @@ struct BridgeJSLink { // To update this file, just rebuild your project or run // `swift package bridge-js`. - export async function createInstantiator(options, swift) { - let instance; - let memory; - let setException; - const textDecoder = new TextDecoder("utf-8"); - const textEncoder = new TextEncoder("utf-8"); + \(topLevelEnumsSection)\(namespaceAssignmentsSection)export async function createInstantiator(options, \(JSGlueVariableScope.reservedSwift)) { + let \(JSGlueVariableScope.reservedInstance); + let \(JSGlueVariableScope.reservedMemory); + let \(JSGlueVariableScope.reservedSetException); + const \(JSGlueVariableScope.reservedTextDecoder) = new TextDecoder("utf-8"); + const \(JSGlueVariableScope.reservedTextEncoder) = new TextEncoder("utf-8"); - let tmpRetString; - let tmpRetBytes; - let tmpRetException; + let \(JSGlueVariableScope.reservedStorageToReturnString); + let \(JSGlueVariableScope.reservedStorageToReturnBytes); + let \(JSGlueVariableScope.reservedStorageToReturnException); return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { - const bytes = new Uint8Array(memory.buffer, ptr, len)\(sharedMemory ? ".slice()" : ""); - tmpRetString = textDecoder.decode(bytes); + const bytes = new Uint8Array(\(JSGlueVariableScope.reservedMemory).buffer, ptr, len)\(sharedMemory ? ".slice()" : ""); + \(JSGlueVariableScope.reservedStorageToReturnString) = \(JSGlueVariableScope.reservedTextDecoder).decode(bytes); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { - const source = swift.memory.getObject(sourceId); - const bytes = new Uint8Array(memory.buffer, bytesPtr); + const source = \(JSGlueVariableScope.reservedSwift).memory.getObject(sourceId); + const bytes = new Uint8Array(\(JSGlueVariableScope.reservedMemory).buffer, bytesPtr); bytes.set(source); } bjs["swift_js_make_js_string"] = function(ptr, len) { - const bytes = new Uint8Array(memory.buffer, ptr, len)\(sharedMemory ? ".slice()" : ""); - return swift.memory.retain(textDecoder.decode(bytes)); + const bytes = new Uint8Array(\(JSGlueVariableScope.reservedMemory).buffer, ptr, len)\(sharedMemory ? ".slice()" : ""); + return \(JSGlueVariableScope.reservedSwift).memory.retain(\(JSGlueVariableScope.reservedTextDecoder).decode(bytes)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { - const target = new Uint8Array(memory.buffer, ptr, len); - target.set(tmpRetBytes); - tmpRetBytes = undefined; + const target = new Uint8Array(\(JSGlueVariableScope.reservedMemory).buffer, ptr, len); + target.set(\(JSGlueVariableScope.reservedStorageToReturnBytes)); + \(JSGlueVariableScope.reservedStorageToReturnBytes) = undefined; } bjs["swift_js_throw"] = function(id) { - tmpRetException = swift.memory.retainByRef(id); + \(JSGlueVariableScope.reservedStorageToReturnException) = \(JSGlueVariableScope.reservedSwift).memory.retainByRef(id); } bjs["swift_js_retain"] = function(id) { - return swift.memory.retainByRef(id); + return \(JSGlueVariableScope.reservedSwift).memory.retainByRef(id); } bjs["swift_js_release"] = function(id) { - swift.memory.release(id); + \(JSGlueVariableScope.reservedSwift).memory.release(id); } + \(renderSwiftClassWrappers().map { $0.indent(count: 12) }.joined(separator: "\n")) \(importObjectBuilders.flatMap { $0.importedLines }.map { $0.indent(count: 12) }.joined(separator: "\n")) }, setInstance: (i) => { - instance = i; - memory = instance.exports.memory; - setException = (error) => { - instance.exports._swift_js_exception.value = swift.memory.retain(error) + \(JSGlueVariableScope.reservedInstance) = i; + \(JSGlueVariableScope.reservedMemory) = \(JSGlueVariableScope.reservedInstance).exports.memory; + \(JSGlueVariableScope.reservedSetException) = (error) => { + \(JSGlueVariableScope.reservedInstance).exports._swift_js_exception.value = \(JSGlueVariableScope.reservedSwift).memory.retain(error) } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { - const js = swift.memory.heap; + const js = \(JSGlueVariableScope.reservedSwift).memory.heap; \(exportsSection) } } """ var dtsLines: [String] = [] - dtsLines.append(contentsOf: namespaceDeclarations()) + dtsLines.append(contentsOf: namespaceDeclarationsLines) dtsLines.append(contentsOf: dtsClassLines) + dtsLines.append(contentsOf: dtsEnumLines) + dtsLines.append(contentsOf: generateImportedTypeDefinitions()) dtsLines.append("export type Exports = {") dtsLines.append(contentsOf: dtsExportLines.map { $0.indent(count: 4) }) dtsLines.append("}") dtsLines.append("export type Imports = {") dtsLines.append(contentsOf: importObjectBuilders.flatMap { $0.dtsImportLines }.map { $0.indent(count: 4) }) dtsLines.append("}") + let topLevelDtsEnumsSection = + topLevelDtsEnumLines.isEmpty ? "" : topLevelDtsEnumLines.joined(separator: "\n") + "\n" + let outputDts = """ // NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, // DO NOT EDIT. @@ -231,7 +309,7 @@ struct BridgeJSLink { // To update this file, just rebuild your project or run // `swift package bridge-js`. - \(dtsLines.joined(separator: "\n")) + \(topLevelDtsEnumsSection)\(dtsLines.joined(separator: "\n")) export function createInstantiator(options: { imports: Imports; }, swift: any): Promise<{ @@ -243,170 +321,132 @@ struct BridgeJSLink { return (outputJs, outputDts) } - private func namespaceDeclarations() -> [String] { - var dtsLines: [String] = [] - var namespaceFunctions: [String: [ExportedFunction]] = [:] - var namespaceClasses: [String: [ExportedClass]] = [:] + private func renderSwiftClassWrappers() -> [String] { + var wrapperLines: [String] = [] + var modulesByName: [String: [ExportedClass]] = [:] + // Group classes by their module name for skeleton in exportedSkeletons { - for function in skeleton.functions { - if let namespace = function.namespace { - let namespaceKey = namespace.joined(separator: ".") - if namespaceFunctions[namespaceKey] == nil { - namespaceFunctions[namespaceKey] = [] - } - namespaceFunctions[namespaceKey]?.append(function) - } - } + if skeleton.classes.isEmpty { continue } - for klass in skeleton.classes { - if let classNamespace = klass.namespace { - let namespaceKey = classNamespace.joined(separator: ".") - if namespaceClasses[namespaceKey] == nil { - namespaceClasses[namespaceKey] = [] - } - namespaceClasses[namespaceKey]?.append(klass) - } + if modulesByName[skeleton.moduleName] == nil { + modulesByName[skeleton.moduleName] = [] } + modulesByName[skeleton.moduleName]?.append(contentsOf: skeleton.classes) } - guard !namespaceFunctions.isEmpty || !namespaceClasses.isEmpty else { return dtsLines } - - dtsLines.append("export {};") - dtsLines.append("") - dtsLines.append("declare global {") - - let identBaseSize = 4 - - for (namespacePath, classes) in namespaceClasses.sorted(by: { $0.key < $1.key }) { - let parts = namespacePath.split(separator: ".").map(String.init) - - for i in 0.. [String] { + var typeDefinitions: [String] = [] - for function in functions { - let signature = - "function \(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType));" - dtsLines.append("\(signature)".indent(count: identBaseSize * (parts.count + 1))) - } + for skeletonSet in importedSkeletons { + for fileSkeleton in skeletonSet.children { + for type in fileSkeleton.types { + typeDefinitions.append("export interface \(type.name) {") + + // Add methods + for method in type.methods { + let methodSignature = + "\(method.name)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: Effects(isAsync: false, isThrows: false)));" + typeDefinitions.append(methodSignature.indent(count: 4)) + } + + // Add properties + for property in type.properties { + let propertySignature = + property.isReadonly + ? "readonly \(property.name): \(property.type.tsType);" + : "\(property.name): \(property.type.tsType);" + typeDefinitions.append(propertySignature.indent(count: 4)) + } - if !namespaceExists { - for i in (0.. String? { + func call(abiName: String, returnType: BridgeType) throws -> String? { + if effects.isAsync { + return try _call(abiName: abiName, returnType: .jsObject(nil)) + } else { + return try _call(abiName: abiName, returnType: returnType) + } + } + + private func _call(abiName: String, returnType: BridgeType) throws -> String? { let call = "instance.exports.\(abiName)(\(parameterForwardings.joined(separator: ", ")))" - var returnExpr: String? - - switch returnType { - case .void: - bodyLines.append("\(call);") - case .string: - bodyLines.append("\(call);") - bodyLines.append("const ret = tmpRetString;") - bodyLines.append("tmpRetString = undefined;") - returnExpr = "ret" - case .int, .float, .double: - bodyLines.append("const ret = \(call);") - returnExpr = "ret" - case .bool: - bodyLines.append("const ret = \(call) !== 0;") - returnExpr = "ret" - case .jsObject: - bodyLines.append("const retId = \(call);") - // TODO: Implement "take" operation - bodyLines.append("const ret = swift.memory.getObject(retId);") - bodyLines.append("swift.memory.release(retId);") - returnExpr = "ret" - case .swiftHeapObject(let name): - bodyLines.append("const ret = new \(name)(\(call));") - returnExpr = "ret" + let liftingFragment = try IntrinsicJSFragment.liftReturn(type: returnType) + assert( + liftingFragment.parameters.count <= 1, + "Lifting fragment should have at most one parameter to lift" + ) + let fragmentArguments: [String] + if liftingFragment.parameters.isEmpty { + body.write("\(call);") + fragmentArguments = [] + } else { + let returnVariable = scope.variable("ret") + body.write("const \(returnVariable) = \(call);") + fragmentArguments = [returnVariable] } - return returnExpr + let liftedValues = liftingFragment.printCode(fragmentArguments, scope, body, cleanupCode) + assert(liftedValues.count <= 1, "Lifting fragment should produce at most one value") + return liftedValues.first } func callConstructor(abiName: String) -> String { - let call = "instance.exports.\(abiName)(\(parameterForwardings.joined(separator: ", ")))" - bodyLines.append("const ret = \(call);") + let call = + "\(JSGlueVariableScope.reservedInstance).exports.\(abiName)(\(parameterForwardings.joined(separator: ", ")))" + body.write("const ret = \(call);") return "ret" } @@ -414,12 +454,13 @@ struct BridgeJSLink { guard effects.isThrows else { return [] } + let exceptionVariable = JSGlueVariableScope.reservedStorageToReturnException return [ - "if (tmpRetException) {", + "if (\(exceptionVariable)) {", // TODO: Implement "take" operation - " const error = swift.memory.getObject(tmpRetException);", - " swift.memory.release(tmpRetException);", - " tmpRetException = undefined;", + " const error = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(exceptionVariable));", + " \(JSGlueVariableScope.reservedSwift).memory.release(\(exceptionVariable));", + " \(exceptionVariable) = undefined;", " throw error;", "}", ] @@ -428,16 +469,15 @@ struct BridgeJSLink { func renderFunction( name: String, parameters: [Parameter], - returnType: BridgeType, returnExpr: String?, - isMethod: Bool + declarationPrefixKeyword: String? ) -> [String] { var funcLines: [String] = [] funcLines.append( - "\(isMethod ? "" : "function ")\(name)(\(parameters.map { $0.name }.joined(separator: ", "))) {" + "\(declarationPrefixKeyword.map { "\($0) "} ?? "")\(name)(\(parameters.map { $0.name }.joined(separator: ", "))) {" ) - funcLines.append(contentsOf: bodyLines.map { $0.indent(count: 4) }) - funcLines.append(contentsOf: cleanupLines.map { $0.indent(count: 4) }) + funcLines.append(contentsOf: body.lines.map { $0.indent(count: 4) }) + funcLines.append(contentsOf: cleanupCode.lines.map { $0.indent(count: 4) }) funcLines.append(contentsOf: checkExceptionLines().map { $0.indent(count: 4) }) if let returnExpr = returnExpr { funcLines.append("return \(returnExpr);".indent(count: 4)) @@ -447,32 +487,165 @@ struct BridgeJSLink { } } - private func renderTSSignature(parameters: [Parameter], returnType: BridgeType) -> String { - return "(\(parameters.map { "\($0.name): \($0.type.tsType)" }.joined(separator: ", "))): \(returnType.tsType)" + private func renderTSSignature(parameters: [Parameter], returnType: BridgeType, effects: Effects) -> String { + let returnTypeWithEffect: String + if effects.isAsync { + returnTypeWithEffect = "Promise<\(returnType.tsType)>" + } else { + returnTypeWithEffect = returnType.tsType + } + return + "(\(parameters.map { "\($0.name): \($0.type.tsType)" }.joined(separator: ", "))): \(returnTypeWithEffect)" } - func renderExportedFunction(function: ExportedFunction) -> (js: [String], dts: [String]) { + func renderExportedEnum(_ enumDefinition: ExportedEnum) throws -> (js: [String], dts: [String]) { + var jsLines: [String] = [] + var dtsLines: [String] = [] + let style: EnumEmitStyle = enumDefinition.emitStyle + + switch enumDefinition.enumType { + case .simple: + jsLines.append("const \(enumDefinition.name) = {") + for (index, enumCase) in enumDefinition.cases.enumerated() { + let caseName = enumCase.name.capitalizedFirstLetter + jsLines.append("\(caseName): \(index),".indent(count: 4)) + } + jsLines.append("};") + jsLines.append("") + + if enumDefinition.namespace == nil { + switch style { + case .tsEnum: + dtsLines.append("export enum \(enumDefinition.name) {") + for (index, enumCase) in enumDefinition.cases.enumerated() { + let caseName = enumCase.name.capitalizedFirstLetter + dtsLines.append("\(caseName) = \(index),".indent(count: 4)) + } + dtsLines.append("}") + dtsLines.append("") + case .const: + dtsLines.append("export const \(enumDefinition.name): {") + for (index, enumCase) in enumDefinition.cases.enumerated() { + let caseName = enumCase.name.capitalizedFirstLetter + dtsLines.append("readonly \(caseName): \(index);".indent(count: 4)) + } + dtsLines.append("};") + dtsLines.append( + "export type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];" + ) + dtsLines.append("") + } + } + case .rawValue: + guard let rawType = enumDefinition.rawType else { + throw BridgeJSLinkError(message: "Raw value enum \(enumDefinition.name) is missing rawType") + } + + jsLines.append("const \(enumDefinition.name) = {") + for enumCase in enumDefinition.cases { + let caseName = enumCase.name.capitalizedFirstLetter + let rawValue = enumCase.rawValue ?? enumCase.name + let formattedValue: String + + if let rawTypeEnum = SwiftEnumRawType.from(rawType) { + switch rawTypeEnum { + case .string: + formattedValue = "\"\(rawValue)\"" + case .bool: + formattedValue = rawValue.lowercased() == "true" ? "true" : "false" + case .float, .double: + formattedValue = rawValue + default: + formattedValue = rawValue + } + } else { + formattedValue = rawValue + } + + jsLines.append("\(caseName): \(formattedValue),".indent(count: 4)) + } + jsLines.append("};") + jsLines.append("") + + if enumDefinition.namespace == nil { + switch style { + case .tsEnum: + dtsLines.append("export enum \(enumDefinition.name) {") + for enumCase in enumDefinition.cases { + let caseName = enumCase.name.capitalizedFirstLetter + let rawValue = enumCase.rawValue ?? enumCase.name + let formattedValue: String + switch rawType { + case "String": formattedValue = "\"\(rawValue)\"" + case "Bool": formattedValue = rawValue.lowercased() == "true" ? "true" : "false" + case "Float", "Double": formattedValue = rawValue + default: formattedValue = rawValue + } + dtsLines.append("\(caseName) = \(formattedValue),".indent(count: 4)) + } + dtsLines.append("}") + dtsLines.append("") + case .const: + dtsLines.append("export const \(enumDefinition.name): {") + for enumCase in enumDefinition.cases { + let caseName = enumCase.name.capitalizedFirstLetter + let rawValue = enumCase.rawValue ?? enumCase.name + let formattedValue: String + + switch rawType { + case "String": + formattedValue = "\"\(rawValue)\"" + case "Bool": + formattedValue = rawValue.lowercased() == "true" ? "true" : "false" + case "Float", "Double": + formattedValue = rawValue + default: + formattedValue = rawValue + } + + dtsLines.append("readonly \(caseName): \(formattedValue);".indent(count: 4)) + } + dtsLines.append("};") + dtsLines.append( + "export type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];" + ) + dtsLines.append("") + } + } + + case .associatedValue: + jsLines.append("// TODO: Implement \(enumDefinition.enumType) enum: \(enumDefinition.name)") + dtsLines.append("// TODO: Implement \(enumDefinition.enumType) enum: \(enumDefinition.name)") + case .namespace: + break + } + + return (jsLines, dtsLines) + } + + func renderExportedFunction(function: ExportedFunction) throws -> (js: [String], dts: [String]) { let thunkBuilder = ExportedThunkBuilder(effects: function.effects) for param in function.parameters { - thunkBuilder.lowerParameter(param: param) + try thunkBuilder.lowerParameter(param: param) } - let returnExpr = thunkBuilder.call(abiName: function.abiName, returnType: function.returnType) + let returnExpr = try thunkBuilder.call(abiName: function.abiName, returnType: function.returnType) let funcLines = thunkBuilder.renderFunction( name: function.abiName, parameters: function.parameters, - returnType: function.returnType, returnExpr: returnExpr, - isMethod: false + declarationPrefixKeyword: "function" ) var dtsLines: [String] = [] dtsLines.append( - "\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType));" + "\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: function.effects));" ) return (funcLines, dtsLines) } - func renderExportedClass(_ klass: ExportedClass) -> (js: [String], dtsType: [String], dtsExportEntry: [String]) { + func renderExportedClass( + _ klass: ExportedClass + ) throws -> (js: [String], dtsType: [String], dtsExportEntry: [String]) { var jsLines: [String] = [] var dtsTypeLines: [String] = [] var dtsExportEntryLines: [String] = [] @@ -481,23 +654,35 @@ struct BridgeJSLink { dtsExportEntryLines.append("\(klass.name): {") jsLines.append("class \(klass.name) extends SwiftHeapObject {") + // Always add __construct and constructor methods for all classes + var constructorLines: [String] = [] + constructorLines.append("static __construct(ptr) {") + constructorLines.append( + "return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_\(klass.name)_deinit, \(klass.name).prototype);" + .indent(count: 4) + ) + constructorLines.append("}") + constructorLines.append("") + jsLines.append(contentsOf: constructorLines.map { $0.indent(count: 4) }) + if let constructor: ExportedConstructor = klass.constructor { let thunkBuilder = ExportedThunkBuilder(effects: constructor.effects) for param in constructor.parameters { - thunkBuilder.lowerParameter(param: param) + try thunkBuilder.lowerParameter(param: param) } var funcLines: [String] = [] + funcLines.append("") funcLines.append("constructor(\(constructor.parameters.map { $0.name }.joined(separator: ", "))) {") let returnExpr = thunkBuilder.callConstructor(abiName: constructor.abiName) - funcLines.append(contentsOf: thunkBuilder.bodyLines.map { $0.indent(count: 4) }) - funcLines.append(contentsOf: thunkBuilder.cleanupLines.map { $0.indent(count: 4) }) + funcLines.append(contentsOf: thunkBuilder.body.lines.map { $0.indent(count: 4) }) + funcLines.append(contentsOf: thunkBuilder.cleanupCode.lines.map { $0.indent(count: 4) }) funcLines.append(contentsOf: thunkBuilder.checkExceptionLines().map { $0.indent(count: 4) }) - funcLines.append("super(\(returnExpr), instance.exports.bjs_\(klass.name)_deinit);".indent(count: 4)) + funcLines.append("return \(klass.name).__construct(\(returnExpr));".indent(count: 4)) funcLines.append("}") jsLines.append(contentsOf: funcLines.map { $0.indent(count: 4) }) dtsExportEntryLines.append( - "new\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name)));" + "new\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name), effects: constructor.effects));" .indent(count: 4) ) } @@ -506,23 +691,70 @@ struct BridgeJSLink { let thunkBuilder = ExportedThunkBuilder(effects: method.effects) thunkBuilder.lowerSelf() for param in method.parameters { - thunkBuilder.lowerParameter(param: param) + try thunkBuilder.lowerParameter(param: param) } - let returnExpr = thunkBuilder.call(abiName: method.abiName, returnType: method.returnType) + let returnExpr = try thunkBuilder.call(abiName: method.abiName, returnType: method.returnType) jsLines.append( contentsOf: thunkBuilder.renderFunction( name: method.name, parameters: method.parameters, - returnType: method.returnType, returnExpr: returnExpr, - isMethod: true + declarationPrefixKeyword: nil + ).map { $0.indent(count: 4) } + ) + dtsTypeLines.append( + "\(method.name)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: method.effects));" + .indent(count: 4) + ) + } + + // Generate property getters and setters + for property in klass.properties { + // Generate getter + let getterThunkBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false)) + getterThunkBuilder.lowerSelf() + let getterReturnExpr = try getterThunkBuilder.call( + abiName: property.getterAbiName(className: klass.name), + returnType: property.type + ) + jsLines.append( + contentsOf: getterThunkBuilder.renderFunction( + name: property.name, + parameters: [], + returnExpr: getterReturnExpr, + declarationPrefixKeyword: "get" ).map { $0.indent(count: 4) } ) + + // Generate setter if not readonly + if !property.isReadonly { + let setterThunkBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false)) + setterThunkBuilder.lowerSelf() + try setterThunkBuilder.lowerParameter( + param: Parameter(label: "value", name: "value", type: property.type) + ) + _ = try setterThunkBuilder.call( + abiName: property.setterAbiName(className: klass.name), + returnType: .void + ) + jsLines.append( + contentsOf: setterThunkBuilder.renderFunction( + name: property.name, + parameters: [.init(label: nil, name: "value", type: property.type)], + returnExpr: nil, + declarationPrefixKeyword: "set" + ).map { $0.indent(count: 4) } + ) + } + + // Add TypeScript property definition + let readonly = property.isReadonly ? "readonly " : "" dtsTypeLines.append( - "\(method.name)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType));" + "\(readonly)\(property.name): \(property.type.tsType);" .indent(count: 4) ) } + jsLines.append("}") dtsTypeLines.append("}") @@ -531,8 +763,11 @@ struct BridgeJSLink { return (jsLines, dtsTypeLines, dtsExportEntryLines) } - func renderGlobalNamespace(namespacedFunctions: [ExportedFunction], namespacedClasses: [ExportedClass]) -> [String] - { + func renderGlobalNamespace( + namespacedFunctions: [ExportedFunction], + namespacedClasses: [ExportedClass], + namespacedEnums: [ExportedEnum] + ) -> [String] { var lines: [String] = [] var uniqueNamespaces: [String] = [] var seen = Set() @@ -545,10 +780,15 @@ struct BridgeJSLink { namespacedClasses .compactMap { $0.namespace } ) + let enumNamespacePaths: Set<[String]> = Set( + namespacedEnums + .compactMap { $0.namespace } + ) let allNamespacePaths = functionNamespacePaths .union(classNamespacePaths) + .union(enumNamespacePaths) allNamespacePaths.forEach { namespacePath in namespacePath.makeIterator().enumerated().forEach { (index, _) in @@ -561,7 +801,7 @@ struct BridgeJSLink { uniqueNamespaces.sorted().forEach { namespace in lines.append("if (typeof globalThis.\(namespace) === 'undefined') {") - lines.append(" globalThis.\(namespace) = {};") + lines.append("globalThis.\(namespace) = {};".indent(count: 4)) lines.append("}") } @@ -570,6 +810,13 @@ struct BridgeJSLink { lines.append("globalThis.\(namespacePath).\(klass.name) = exports.\(klass.name);") } + namespacedEnums.forEach { enumDefinition in + if enumDefinition.enumType == .associatedValue { + let namespacePath: String = enumDefinition.namespace?.joined(separator: ".") ?? "" + lines.append("globalThis.\(namespacePath).\(enumDefinition.name) = exports.\(enumDefinition.name);") + } + } + namespacedFunctions.forEach { function in let namespacePath: String = function.namespace?.joined(separator: ".") ?? "" lines.append("globalThis.\(namespacePath).\(function.name) = exports.\(function.name);") @@ -579,28 +826,38 @@ struct BridgeJSLink { } class ImportedThunkBuilder { - var bodyLines: [String] = [] + let body: CodeFragmentPrinter + let scope: JSGlueVariableScope + let cleanupCode: CodeFragmentPrinter var parameterNames: [String] = [] var parameterForwardings: [String] = [] + init() { + self.body = CodeFragmentPrinter() + self.scope = JSGlueVariableScope() + self.cleanupCode = CodeFragmentPrinter() + } + func liftSelf() { parameterNames.append("self") } - func liftParameter(param: Parameter) { - parameterNames.append(param.name) - switch param.type { - case .string: - let stringObjectName = "\(param.name)Object" - // TODO: Implement "take" operation - bodyLines.append("const \(stringObjectName) = swift.memory.getObject(\(param.name));") - bodyLines.append("swift.memory.release(\(param.name));") - parameterForwardings.append(stringObjectName) - case .jsObject: - parameterForwardings.append("swift.memory.getObject(\(param.name))") - default: - parameterForwardings.append(param.name) + func liftParameter(param: Parameter) throws { + let liftingFragment = try IntrinsicJSFragment.liftParameter(type: param.type) + assert( + liftingFragment.parameters.count >= 1, + "Lifting fragment should have at least one parameter to lift" + ) + let valuesToLift: [String] + if liftingFragment.parameters.count == 1 { + parameterNames.append(param.name) + valuesToLift = [scope.variable(param.name)] + } else { + valuesToLift = liftingFragment.parameters.map { scope.variable(param.name + $0.capitalizedFirstLetter) } } + let liftedValues = liftingFragment.printCode(valuesToLift, scope, body, cleanupCode) + assert(liftedValues.count == 1, "Lifting fragment should produce exactly one value") + parameterForwardings.append(contentsOf: liftedValues) } func renderFunction( @@ -613,7 +870,7 @@ struct BridgeJSLink { "function \(name)(\(parameterNames.joined(separator: ", "))) {" ) funcLines.append("try {".indent(count: 4)) - funcLines.append(contentsOf: bodyLines.map { $0.indent(count: 8) }) + funcLines.append(contentsOf: body.lines.map { $0.indent(count: 8) }) if let returnExpr = returnExpr { funcLines.append("return \(returnExpr);".indent(count: 8)) } @@ -627,55 +884,69 @@ struct BridgeJSLink { return funcLines } - func call(name: String, returnType: BridgeType) { - let call = "options.imports.\(name)(\(parameterForwardings.joined(separator: ", ")))" - if returnType == .void { - bodyLines.append("\(call);") - } else { - bodyLines.append("let ret = \(call);") - } + func call(name: String, returnType: BridgeType) throws -> String? { + return try self.call(calleeExpr: "imports.\(name)", returnType: returnType) } - func callConstructor(name: String) { - let call = "new options.imports.\(name)(\(parameterForwardings.joined(separator: ", ")))" - bodyLines.append("let ret = \(call);") + private func call(calleeExpr: String, returnType: BridgeType) throws -> String? { + let callExpr = "\(calleeExpr)(\(parameterForwardings.joined(separator: ", ")))" + return try self.call(callExpr: callExpr, returnType: returnType) } - func callMethod(name: String, returnType: BridgeType) { - let call = "swift.memory.getObject(self).\(name)(\(parameterForwardings.joined(separator: ", ")))" - if returnType == .void { - bodyLines.append("\(call);") + private func call(callExpr: String, returnType: BridgeType) throws -> String? { + let loweringFragment = try IntrinsicJSFragment.lowerReturn(type: returnType) + let returnExpr: String? + if loweringFragment.parameters.count == 0 { + body.write("\(callExpr);") + returnExpr = nil } else { - bodyLines.append("let ret = \(call);") + let resultVariable = scope.variable("ret") + body.write("let \(resultVariable) = \(callExpr);") + returnExpr = resultVariable } + return try lowerReturnValue( + returnType: returnType, + returnExpr: returnExpr, + loweringFragment: loweringFragment + ) } - func callPropertyGetter(name: String, returnType: BridgeType) { - let call = "swift.memory.getObject(self).\(name)" - bodyLines.append("let ret = \(call);") + func callConstructor(name: String) throws -> String? { + let call = "new imports.\(name)(\(parameterForwardings.joined(separator: ", ")))" + let type: BridgeType = .jsObject(name) + let loweringFragment = try IntrinsicJSFragment.lowerReturn(type: type) + return try lowerReturnValue(returnType: type, returnExpr: call, loweringFragment: loweringFragment) + } + + func callMethod(name: String, returnType: BridgeType) throws -> String? { + return try call( + calleeExpr: "\(JSGlueVariableScope.reservedSwift).memory.getObject(self).\(name)", + returnType: returnType + ) + } + + func callPropertyGetter(name: String, returnType: BridgeType) throws -> String? { + return try call( + callExpr: "\(JSGlueVariableScope.reservedSwift).memory.getObject(self).\(name)", + returnType: returnType + ) } func callPropertySetter(name: String, returnType: BridgeType) { - let call = "swift.memory.getObject(self).\(name) = \(parameterForwardings.joined(separator: ", "))" - bodyLines.append("\(call);") - } - - func lowerReturnValue(returnType: BridgeType) throws -> String? { - switch returnType { - case .void: - return nil - case .string: - bodyLines.append("tmpRetBytes = textEncoder.encode(ret);") - return "tmpRetBytes.length" - case .int, .float, .double: - return "ret" - case .bool: - return "ret !== 0" - case .jsObject: - return "swift.memory.retain(ret)" - case .swiftHeapObject: - throw BridgeJSLinkError(message: "Swift heap object is not supported in imported functions") - } + let call = + "\(JSGlueVariableScope.reservedSwift).memory.getObject(self).\(name) = \(parameterForwardings.joined(separator: ", "))" + body.write("\(call);") + } + + private func lowerReturnValue( + returnType: BridgeType, + returnExpr: String?, + loweringFragment: IntrinsicJSFragment + ) throws -> String? { + assert(loweringFragment.parameters.count <= 1, "Lowering fragment should have at most one parameter") + let loweredValues = loweringFragment.printCode(returnExpr.map { [$0] } ?? [], scope, body, cleanupCode) + assert(loweredValues.count <= 1, "Lowering fragment should produce at most one value") + return loweredValues.first } } @@ -686,7 +957,9 @@ struct BridgeJSLink { init(moduleName: String) { self.moduleName = moduleName - importedLines.append("const \(moduleName) = importObject[\"\(moduleName)\"] = {};") + importedLines.append( + "const \(moduleName) = importObject[\"\(moduleName)\"] = importObject[\"\(moduleName)\"] || {};" + ) } func assignToImportObject(name: String, function: [String]) { @@ -700,24 +973,369 @@ struct BridgeJSLink { } } + struct NamespaceBuilder { + + /// Generates JavaScript code for setting up global namespace structure + /// + /// This function creates the necessary JavaScript code to properly expose namespaced + /// functions, classes, and enums on the global object (globalThis). It ensures that + /// nested namespace paths are created correctly and that all exported items are + /// accessible through their full namespace paths. + /// + /// For example, if you have @JS("Utils.Math") func add() it will generate code that + /// makes globalThis.Utils.Math.add accessible. + /// + /// - Parameters: + /// - namespacedFunctions: Functions annotated with @JS("namespace.path") + /// - namespacedClasses: Classes annotated with @JS("namespace.path") + /// - namespacedEnums: Enums annotated with @JS("namespace.path") + /// - Returns: Array of JavaScript code lines that set up the global namespace structure + func renderGlobalNamespace( + namespacedFunctions: [ExportedFunction], + namespacedClasses: [ExportedClass], + namespacedEnums: [ExportedEnum] + ) -> [String] { + var lines: [String] = [] + var uniqueNamespaces: [String] = [] + var seen = Set() + + let functionNamespacePaths: Set<[String]> = Set( + namespacedFunctions + .compactMap { $0.namespace } + ) + let classNamespacePaths: Set<[String]> = Set( + namespacedClasses + .compactMap { $0.namespace } + ) + let enumNamespacePaths: Set<[String]> = Set( + namespacedEnums + .compactMap { $0.namespace } + ) + + let allNamespacePaths = + functionNamespacePaths + .union(classNamespacePaths) + .union(enumNamespacePaths) + + allNamespacePaths.forEach { namespacePath in + namespacePath.makeIterator().enumerated().forEach { (index, _) in + let path = namespacePath[0...index].joined(separator: ".") + if seen.insert(path).inserted { + uniqueNamespaces.append(path) + } + } + } + + uniqueNamespaces.sorted().forEach { namespace in + lines.append("if (typeof globalThis.\(namespace) === 'undefined') {") + lines.append("globalThis.\(namespace) = {};".indent(count: 4)) + lines.append("}") + } + + namespacedClasses.forEach { klass in + let namespacePath: String = klass.namespace?.joined(separator: ".") ?? "" + lines.append("globalThis.\(namespacePath).\(klass.name) = exports.\(klass.name);") + } + + namespacedEnums.forEach { enumDefinition in + let namespacePath: String = enumDefinition.namespace?.joined(separator: ".") ?? "" + lines.append("globalThis.\(namespacePath).\(enumDefinition.name) = exports.\(enumDefinition.name);") + } + + namespacedFunctions.forEach { function in + let namespacePath: String = function.namespace?.joined(separator: ".") ?? "" + lines.append("globalThis.\(namespacePath).\(function.name) = exports.\(function.name);") + } + + return lines + } + + func renderTopLevelEnumNamespaceAssignments(namespacedEnums: [ExportedEnum]) -> [String] { + let topLevelNamespacedEnums = namespacedEnums.filter { $0.enumType == .simple || $0.enumType == .rawValue } + + guard !topLevelNamespacedEnums.isEmpty else { return [] } + + var lines: [String] = [] + var uniqueNamespaces: [String] = [] + var seen = Set() + + for enumDef in topLevelNamespacedEnums { + guard let namespacePath = enumDef.namespace else { continue } + namespacePath.enumerated().forEach { (index, _) in + let path = namespacePath[0...index].joined(separator: ".") + if !seen.contains(path) { + seen.insert(path) + uniqueNamespaces.append(path) + } + } + } + + for namespace in uniqueNamespaces { + lines.append("if (typeof globalThis.\(namespace) === 'undefined') {") + lines.append("globalThis.\(namespace) = {};".indent(count: 4)) + lines.append("}") + } + + if !lines.isEmpty { + lines.append("") + } + + for enumDef in topLevelNamespacedEnums { + let namespacePath = enumDef.namespace?.joined(separator: ".") ?? "" + lines.append("globalThis.\(namespacePath).\(enumDef.name) = \(enumDef.name);") + } + + return lines + } + + private struct NamespaceContent { + var functions: [ExportedFunction] = [] + var classes: [ExportedClass] = [] + var enums: [ExportedEnum] = [] + } + + private final class NamespaceNode { + let name: String + var children: [String: NamespaceNode] = [:] + var content: NamespaceContent = NamespaceContent() + + init(name: String) { + self.name = name + } + + func addChild(_ childName: String) -> NamespaceNode { + if let existing = children[childName] { + return existing + } + let newChild = NamespaceNode(name: childName) + children[childName] = newChild + return newChild + } + } + + /// Generates TypeScript declarations for all namespaces + /// + /// This function enables properly grouping all Swift code within given namespaces + /// regardless of location in Swift input files. It uses a tree-based structure to + /// properly create unique namespace declarations that avoid namespace duplication in TS and generate + /// predictable declarations in sorted order. + /// + /// The function collects all namespaced items (functions, classes, enums) from the + /// exported skeletons and builds a hierarchical namespace tree. It then traverses + /// this tree to generate TypeScript namespace declarations that mirror the Swift + /// namespace structure. + /// - Parameters: + /// - exportedSkeletons: Exported Swift structures to generate namespaces for + /// - renderTSSignatureCallback: closure to generate TS signature that aligns with rest of codebase + /// - Returns: Array of TypeScript declaration lines defining the global namespace structure + func namespaceDeclarations( + exportedSkeletons: [ExportedSkeleton], + renderTSSignatureCallback: @escaping ([Parameter], BridgeType, Effects) -> String + ) -> [String] { + var dtsLines: [String] = [] + + let rootNode = NamespaceNode(name: "") + + for skeleton in exportedSkeletons { + for function in skeleton.functions { + if let namespace = function.namespace { + var currentNode = rootNode + for part in namespace { + currentNode = currentNode.addChild(part) + } + currentNode.content.functions.append(function) + } + } + + for klass in skeleton.classes { + if let classNamespace = klass.namespace { + var currentNode = rootNode + for part in classNamespace { + currentNode = currentNode.addChild(part) + } + currentNode.content.classes.append(klass) + } + } + + for enumDefinition in skeleton.enums { + if let enumNamespace = enumDefinition.namespace, enumDefinition.enumType != .namespace { + var currentNode = rootNode + for part in enumNamespace { + currentNode = currentNode.addChild(part) + } + currentNode.content.enums.append(enumDefinition) + } + } + } + + guard !rootNode.children.isEmpty else { + return dtsLines + } + + dtsLines.append("export {};") + dtsLines.append("") + dtsLines.append("declare global {") + + let identBaseSize = 4 + + func generateNamespaceDeclarations(node: NamespaceNode, depth: Int) { + let sortedChildren = node.children.sorted { $0.key < $1.key } + + for (childName, childNode) in sortedChildren { + dtsLines.append("namespace \(childName) {".indent(count: identBaseSize * depth)) + + let contentDepth = depth + 1 + + let sortedClasses = childNode.content.classes.sorted { $0.name < $1.name } + for klass in sortedClasses { + dtsLines.append("class \(klass.name) {".indent(count: identBaseSize * contentDepth)) + + if let constructor = klass.constructor { + let constructorSignature = + "constructor(\(constructor.parameters.map { "\($0.name): \($0.type.tsType)" }.joined(separator: ", ")));" + dtsLines.append("\(constructorSignature)".indent(count: identBaseSize * (contentDepth + 1))) + } + + let sortedMethods = klass.methods.sorted { $0.name < $1.name } + for method in sortedMethods { + let methodSignature = + "\(method.name)\(renderTSSignatureCallback(method.parameters, method.returnType, method.effects));" + dtsLines.append("\(methodSignature)".indent(count: identBaseSize * (contentDepth + 1))) + } + + dtsLines.append("}".indent(count: identBaseSize * contentDepth)) + } + + let sortedEnums = childNode.content.enums.sorted { $0.name < $1.name } + for enumDefinition in sortedEnums { + let style: EnumEmitStyle = enumDefinition.emitStyle + switch enumDefinition.enumType { + case .simple: + switch style { + case .tsEnum: + dtsLines.append( + "enum \(enumDefinition.name) {".indent(count: identBaseSize * contentDepth) + ) + for (index, enumCase) in enumDefinition.cases.enumerated() { + let caseName = enumCase.name.capitalizedFirstLetter + dtsLines.append( + "\(caseName) = \(index),".indent(count: identBaseSize * (contentDepth + 1)) + ) + } + dtsLines.append("}".indent(count: identBaseSize * contentDepth)) + case .const: + dtsLines.append( + "const \(enumDefinition.name): {".indent(count: identBaseSize * contentDepth) + ) + for (index, enumCase) in enumDefinition.cases.enumerated() { + let caseName = enumCase.name.capitalizedFirstLetter + dtsLines.append( + "readonly \(caseName): \(index);".indent( + count: identBaseSize * (contentDepth + 1) + ) + ) + } + dtsLines.append("};".indent(count: identBaseSize * contentDepth)) + dtsLines.append( + "type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];" + .indent(count: identBaseSize * contentDepth) + ) + } + case .rawValue: + guard let rawType = enumDefinition.rawType else { continue } + switch style { + case .tsEnum: + dtsLines.append( + "enum \(enumDefinition.name) {".indent(count: identBaseSize * contentDepth) + ) + for enumCase in enumDefinition.cases { + let caseName = enumCase.name.capitalizedFirstLetter + let rawValue = enumCase.rawValue ?? enumCase.name + let formattedValue: String + switch rawType { + case "String": formattedValue = "\"\(rawValue)\"" + case "Bool": formattedValue = rawValue.lowercased() == "true" ? "true" : "false" + case "Float", "Double": formattedValue = rawValue + default: formattedValue = rawValue + } + dtsLines.append( + "\(caseName) = \(formattedValue),".indent( + count: identBaseSize * (contentDepth + 1) + ) + ) + } + dtsLines.append("}".indent(count: identBaseSize * contentDepth)) + case .const: + dtsLines.append( + "const \(enumDefinition.name): {".indent(count: identBaseSize * contentDepth) + ) + for enumCase in enumDefinition.cases { + let caseName = enumCase.name.capitalizedFirstLetter + let rawValue = enumCase.rawValue ?? enumCase.name + let formattedValue: String + switch rawType { + case "String": formattedValue = "\"\(rawValue)\"" + case "Bool": formattedValue = rawValue.lowercased() == "true" ? "true" : "false" + case "Float", "Double": formattedValue = rawValue + default: formattedValue = rawValue + } + dtsLines.append( + "readonly \(caseName): \(formattedValue);".indent( + count: identBaseSize * (contentDepth + 1) + ) + ) + } + dtsLines.append("};".indent(count: identBaseSize * contentDepth)) + dtsLines.append( + "type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];" + .indent(count: identBaseSize * contentDepth) + ) + } + case .associatedValue, .namespace: + continue + } + } + + let sortedFunctions = childNode.content.functions.sorted { $0.name < $1.name } + for function in sortedFunctions { + let signature = + "\(function.name)\(renderTSSignatureCallback(function.parameters, function.returnType, function.effects));" + dtsLines.append("\(signature)".indent(count: identBaseSize * contentDepth)) + } + + generateNamespaceDeclarations(node: childNode, depth: contentDepth) + + dtsLines.append("}".indent(count: identBaseSize * depth)) + } + } + + generateNamespaceDeclarations(node: rootNode, depth: 1) + + dtsLines.append("}") + dtsLines.append("") + + return dtsLines + } + } + func renderImportedFunction( importObjectBuilder: ImportObjectBuilder, function: ImportedFunctionSkeleton ) throws { let thunkBuilder = ImportedThunkBuilder() for param in function.parameters { - thunkBuilder.liftParameter(param: param) + try thunkBuilder.liftParameter(param: param) } - thunkBuilder.call(name: function.name, returnType: function.returnType) - let returnExpr = try thunkBuilder.lowerReturnValue(returnType: function.returnType) + let returnExpr = try thunkBuilder.call(name: function.name, returnType: function.returnType) let funcLines = thunkBuilder.renderFunction( name: function.abiName(context: nil), returnExpr: returnExpr, returnType: function.returnType ) + let effects = Effects(isAsync: false, isThrows: false) importObjectBuilder.appendDts( [ - "\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType));" + "\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: effects));" ] ) importObjectBuilder.assignToImportObject(name: function.abiName(context: nil), function: funcLines) @@ -740,8 +1358,7 @@ struct BridgeJSLink { property: property, abiName: getterAbiName, emitCall: { thunkBuilder in - thunkBuilder.callPropertyGetter(name: property.name, returnType: property.type) - return try thunkBuilder.lowerReturnValue(returnType: property.type) + return try thunkBuilder.callPropertyGetter(name: property.name, returnType: property.type) } ) importObjectBuilder.assignToImportObject(name: getterAbiName, function: js) @@ -753,7 +1370,7 @@ struct BridgeJSLink { property: property, abiName: setterAbiName, emitCall: { thunkBuilder in - thunkBuilder.liftParameter( + try thunkBuilder.liftParameter( param: Parameter(label: nil, name: "newValue", type: property.type) ) thunkBuilder.callPropertySetter(name: property.name, returnType: property.type) @@ -778,11 +1395,10 @@ struct BridgeJSLink { ) throws { let thunkBuilder = ImportedThunkBuilder() for param in constructor.parameters { - thunkBuilder.liftParameter(param: param) + try thunkBuilder.liftParameter(param: param) } let returnType = BridgeType.jsObject(type.name) - thunkBuilder.callConstructor(name: type.name) - let returnExpr = try thunkBuilder.lowerReturnValue(returnType: returnType) + let returnExpr = try thunkBuilder.callConstructor(name: type.name) let abiName = constructor.abiName(context: type) let funcLines = thunkBuilder.renderFunction( name: abiName, @@ -792,7 +1408,8 @@ struct BridgeJSLink { importObjectBuilder.assignToImportObject(name: abiName, function: funcLines) importObjectBuilder.appendDts([ "\(type.name): {", - "new\(renderTSSignature(parameters: constructor.parameters, returnType: returnType));".indent(count: 4), + "new\(renderTSSignature(parameters: constructor.parameters, returnType: returnType, effects: Effects(isAsync: false, isThrows: false)));" + .indent(count: 4), "}", ]) } @@ -820,10 +1437,9 @@ struct BridgeJSLink { let thunkBuilder = ImportedThunkBuilder() thunkBuilder.liftSelf() for param in method.parameters { - thunkBuilder.liftParameter(param: param) + try thunkBuilder.liftParameter(param: param) } - thunkBuilder.callMethod(name: method.name, returnType: method.returnType) - let returnExpr = try thunkBuilder.lowerReturnValue(returnType: method.returnType) + let returnExpr = try thunkBuilder.callMethod(name: method.name, returnType: method.returnType) let funcLines = thunkBuilder.renderFunction( name: method.abiName(context: context), returnExpr: returnExpr, @@ -862,6 +1478,14 @@ extension BridgeType { return name ?? "any" case .swiftHeapObject(let name): return name + case .caseEnum(let name): + return name + case .rawValueEnum(let name, _): + return name + case .associatedValueEnum(let name): + return name + case .namespaceEnum(let name): + return name } } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/CodeFragmentPrinter.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/CodeFragmentPrinter.swift new file mode 100644 index 00000000..c4624e69 --- /dev/null +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/CodeFragmentPrinter.swift @@ -0,0 +1,41 @@ +/// A printer for code fragments. +final class CodeFragmentPrinter { + private(set) var lines: [String] = [] + private var indentLevel: Int = 0 + + init(header: String = "") { + self.lines.append(contentsOf: header.split(separator: "\n").map { String($0) }) + } + + func nextLine() { + lines.append("") + } + + func write(_ line: S) { + lines.append(String(repeating: " ", count: indentLevel * 4) + String(line)) + } + + func write(lines: [String]) { + for line in lines { + write(line) + } + } + + func write(contentsOf printer: CodeFragmentPrinter) { + self.write(lines: printer.lines) + } + + func indent() { + indentLevel += 1 + } + + func unindent() { + indentLevel -= 1 + } + + func indent(_ body: () throws -> Void) rethrows { + indentLevel += 1 + try body() + indentLevel -= 1 + } +} diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift new file mode 100644 index 00000000..ae74844f --- /dev/null +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift @@ -0,0 +1,317 @@ +#if canImport(BridgeJSSkeleton) +import BridgeJSSkeleton +#endif + +/// A scope for variables for JS glue code +final class JSGlueVariableScope { + // MARK: - Reserved variables + + static let reservedSwift = "swift" + static let reservedInstance = "instance" + static let reservedMemory = "memory" + static let reservedSetException = "setException" + static let reservedStorageToReturnString = "tmpRetString" + static let reservedStorageToReturnBytes = "tmpRetBytes" + static let reservedStorageToReturnException = "tmpRetException" + static let reservedTextEncoder = "textEncoder" + static let reservedTextDecoder = "textDecoder" + + private var variables: Set = [ + reservedSwift, + reservedMemory, + reservedStorageToReturnString, + reservedStorageToReturnBytes, + reservedStorageToReturnException, + reservedTextEncoder, + reservedTextDecoder, + ] + + /// Returns a unique variable name in the scope based on the given name hint. + /// + /// - Parameter hint: A hint for the variable name. + /// - Returns: A unique variable name. + func variable(_ hint: String) -> String { + if variables.insert(hint).inserted { + return hint + } + var suffixedName: String + var suffix = 1 + repeat { + suffixedName = hint + suffix.description + suffix += 1 + } while !variables.insert(suffixedName).inserted + return suffixedName + } +} + +/// A fragment of JS code used to convert a value between Swift and JS. +/// +/// See `BridgeJSInstrincics.swift` in the main JavaScriptKit module for Swift side lowering/lifting implementation. +struct IntrinsicJSFragment: Sendable { + /// The names of the parameters that the fragment expects. + let parameters: [String] + + /// Prints the fragment code. + /// + /// - Parameters: + /// - arguments: The arguments that the fragment expects. An argument may be an expression with side effects, + /// so the callee is responsible for evaluating the arguments only once. + /// - scope: The scope of the variables. + /// - printer: The printer to print the main fragment code. + /// - cleanupCode: The printer to print the code that is expected to be executed at the end of the caller of the + /// fragment. + /// - Returns: List of result expressions. + let printCode: + @Sendable ( + _ arguments: [String], + _ scope: JSGlueVariableScope, + _ printer: CodeFragmentPrinter, + _ cleanupCode: CodeFragmentPrinter + ) -> [String] + + /// A fragment that does nothing + static let void = IntrinsicJSFragment( + parameters: [], + printCode: { _, _, _, _ in + return [] + } + ) + + /// A fragment that returns the argument as is. + static let identity = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + return [arguments[0]] + } + ) + + /// NOTE: JavaScript engine itself converts booleans to integers when passing them to + /// Wasm functions, so we don't need to do anything here + static let boolLowerParameter = identity + static let boolLiftReturn = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + return ["\(arguments[0]) !== 0"] + } + ) + static let boolLiftParameter = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + return ["\(arguments[0]) !== 0"] + } + ) + static let boolLowerReturn = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + return ["\(arguments[0]) ? 1 : 0"] + } + ) + + static let stringLowerParameter = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + let argument = arguments[0] + let bytesLabel = scope.variable("\(argument)Bytes") + let bytesIdLabel = scope.variable("\(argument)Id") + printer.write("const \(bytesLabel) = \(JSGlueVariableScope.reservedTextEncoder).encode(\(argument));") + printer.write("const \(bytesIdLabel) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesLabel));") + cleanupCode.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(bytesIdLabel));") + return [bytesIdLabel, "\(bytesLabel).length"] + } + ) + static let stringLiftReturn = IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanupCode in + let resultLabel = scope.variable("ret") + printer.write("const \(resultLabel) = \(JSGlueVariableScope.reservedStorageToReturnString);") + printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = undefined;") + return [resultLabel] + } + ) + static let stringLiftParameter = IntrinsicJSFragment( + parameters: ["objectId"], + printCode: { arguments, scope, printer, cleanupCode in + let objectId = arguments[0] + let objectLabel = scope.variable("\(objectId)Object") + // TODO: Implement "take" operation + printer.write("const \(objectLabel) = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(objectId));") + printer.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(objectId));") + return [objectLabel] + } + ) + static let stringLowerReturn = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + printer.write( + "\(JSGlueVariableScope.reservedStorageToReturnBytes) = \(JSGlueVariableScope.reservedTextEncoder).encode(\(arguments[0]));" + ) + return ["\(JSGlueVariableScope.reservedStorageToReturnBytes).length"] + } + ) + + static let jsObjectLowerParameter = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + return ["swift.memory.retain(\(arguments[0]))"] + } + ) + static let jsObjectLiftReturn = IntrinsicJSFragment( + parameters: ["retId"], + printCode: { arguments, scope, printer, cleanupCode in + // TODO: Implement "take" operation + let resultLabel = scope.variable("ret") + let retId = arguments[0] + printer.write("const \(resultLabel) = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(retId));") + printer.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(retId));") + return [resultLabel] + } + ) + static let jsObjectLiftParameter = IntrinsicJSFragment( + parameters: ["objectId"], + printCode: { arguments, scope, printer, cleanupCode in + return ["\(JSGlueVariableScope.reservedSwift).memory.getObject(\(arguments[0]))"] + } + ) + static let jsObjectLowerReturn = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + return ["\(JSGlueVariableScope.reservedSwift).memory.retain(\(arguments[0]))"] + } + ) + + static let swiftHeapObjectLowerParameter = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + return ["\(arguments[0]).pointer"] + } + ) + static func swiftHeapObjectLiftReturn(_ name: String) -> IntrinsicJSFragment { + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + return ["\(name).__construct(\(arguments[0]))"] + } + ) + } + + // MARK: - ExportSwift + + /// Returns a fragment that lowers a JS value to Wasm core values for parameters + static func lowerParameter(type: BridgeType) throws -> IntrinsicJSFragment { + switch type { + case .int, .float, .double, .bool: return .identity + case .string: return .stringLowerParameter + case .jsObject: return .jsObjectLowerParameter + case .swiftHeapObject: + return .swiftHeapObjectLowerParameter + case .void: return .void + case .caseEnum: return .identity + case .rawValueEnum(_, let rawType): + switch rawType { + case .string: return .stringLowerParameter + default: return .identity + } + case .associatedValueEnum(let string): + throw BridgeJSLinkError( + message: "Associated value enums are not supported to be passed as parameters: \(string)" + ) + case .namespaceEnum(let string): + throw BridgeJSLinkError(message: "Namespace enums are not supported to be passed as parameters: \(string)") + } + } + + /// Returns a fragment that lifts a Wasm core value to a JS value for return values + static func liftReturn(type: BridgeType) throws -> IntrinsicJSFragment { + switch type { + case .int, .float, .double: return .identity + case .bool: return .boolLiftReturn + case .string: return .stringLiftReturn + case .jsObject: return .jsObjectLiftReturn + case .swiftHeapObject(let name): return .swiftHeapObjectLiftReturn(name) + case .void: return .void + case .caseEnum: return .identity + case .rawValueEnum(_, let rawType): + switch rawType { + case .string: return .stringLiftReturn + case .bool: return .boolLiftReturn + default: return .identity + } + case .associatedValueEnum(let string): + throw BridgeJSLinkError( + message: "Associated value enums are not supported to be returned from functions: \(string)" + ) + case .namespaceEnum(let string): + throw BridgeJSLinkError( + message: "Namespace enums are not supported to be returned from functions: \(string)" + ) + } + } + + // MARK: - ImportedJS + + /// Returns a fragment that lifts Wasm core values to JS values for parameters + static func liftParameter(type: BridgeType) throws -> IntrinsicJSFragment { + switch type { + case .int, .float, .double: return .identity + case .bool: return .boolLiftParameter + case .string: return .stringLiftParameter + case .jsObject: return .jsObjectLiftParameter + case .swiftHeapObject(let name): + throw BridgeJSLinkError( + message: + "Swift heap objects are not supported to be passed as parameters to imported JS functions: \(name)" + ) + case .void: + throw BridgeJSLinkError( + message: "Void can't appear in parameters of imported JS functions" + ) + case .caseEnum: return .identity + case .rawValueEnum(_, let rawType): + switch rawType { + case .string: return .stringLiftParameter + case .bool: return .boolLiftParameter + default: return .identity + } + case .associatedValueEnum(let string): + throw BridgeJSLinkError( + message: + "Associated value enums are not supported to be passed as parameters to imported JS functions: \(string)" + ) + case .namespaceEnum(let string): + throw BridgeJSLinkError( + message: + "Namespace enums are not supported to be passed as parameters to imported JS functions: \(string)" + ) + } + } + + /// Returns a fragment that lowers a JS value to Wasm core values for return values + static func lowerReturn(type: BridgeType) throws -> IntrinsicJSFragment { + switch type { + case .int, .float, .double: return .identity + case .bool: return .boolLowerReturn + case .string: return .stringLowerReturn + case .jsObject: return .jsObjectLowerReturn + case .swiftHeapObject: + throw BridgeJSLinkError( + message: "Swift heap objects are not supported to be returned from imported JS functions" + ) + case .void: return .void + case .caseEnum: return .identity + case .rawValueEnum(_, let rawType): + switch rawType { + case .string: return .stringLowerReturn + case .bool: return .boolLowerReturn + default: return .identity + } + case .associatedValueEnum(let string): + throw BridgeJSLinkError( + message: "Associated value enums are not supported to be returned from imported JS functions: \(string)" + ) + case .namespaceEnum(let string): + throw BridgeJSLinkError( + message: "Namespace enums are not supported to be returned from imported JS functions: \(string)" + ) + } + } +} diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 56e88f92..5fa0acb9 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -2,111 +2,315 @@ // MARK: - Types -enum BridgeType: Codable, Equatable { +public enum BridgeType: Codable, Equatable { case int, float, double, string, bool, jsObject(String?), swiftHeapObject(String), void + case caseEnum(String) + case rawValueEnum(String, SwiftEnumRawType) + case associatedValueEnum(String) + case namespaceEnum(String) } -enum WasmCoreType: String, Codable { +public enum WasmCoreType: String, Codable, Sendable { case i32, i64, f32, f64, pointer } -struct Parameter: Codable { - let label: String? - let name: String - let type: BridgeType +public enum SwiftEnumRawType: String, CaseIterable, Codable { + case string = "String" + case bool = "Bool" + case int = "Int" + case int32 = "Int32" + case int64 = "Int64" + case uint = "UInt" + case uint32 = "UInt32" + case uint64 = "UInt64" + case float = "Float" + case double = "Double" + + public var wasmCoreType: WasmCoreType? { + switch self { + case .string: + return nil + case .bool, .int, .int32, .uint, .uint32: + return .i32 + case .int64, .uint64: + return .i64 + case .float: + return .f32 + case .double: + return .f64 + } + } + + public static func from(_ rawTypeString: String) -> SwiftEnumRawType? { + return Self.allCases.first { $0.rawValue == rawTypeString } + } +} + +public struct Parameter: Codable { + public let label: String? + public let name: String + public let type: BridgeType + + public init(label: String?, name: String, type: BridgeType) { + self.label = label + self.name = name + self.type = type + } +} + +public struct Effects: Codable { + public var isAsync: Bool + public var isThrows: Bool + + public init(isAsync: Bool, isThrows: Bool) { + self.isAsync = isAsync + self.isThrows = isThrows + } +} + +// MARK: - Enum Skeleton + +public struct AssociatedValue: Codable, Equatable { + public let label: String? + public let type: BridgeType + + public init(label: String?, type: BridgeType) { + self.label = label + self.type = type + } +} + +public struct EnumCase: Codable, Equatable { + public let name: String + public let rawValue: String? + public let associatedValues: [AssociatedValue] + + public var isSimple: Bool { + associatedValues.isEmpty + } + + public init(name: String, rawValue: String?, associatedValues: [AssociatedValue]) { + self.name = name + self.rawValue = rawValue + self.associatedValues = associatedValues + } +} + +public enum EnumEmitStyle: String, Codable { + case const + case tsEnum +} + +public struct ExportedEnum: Codable, Equatable { + public let name: String + public let swiftCallName: String + public let explicitAccessControl: String? + public let cases: [EnumCase] + public let rawType: String? + public let namespace: [String]? + public let emitStyle: EnumEmitStyle + public var enumType: EnumType { + if cases.isEmpty { + return .namespace + } else if cases.allSatisfy(\.isSimple) { + return rawType != nil ? .rawValue : .simple + } else { + return .associatedValue + } + } + + public init( + name: String, + swiftCallName: String, + explicitAccessControl: String?, + cases: [EnumCase], + rawType: String?, + namespace: [String]?, + emitStyle: EnumEmitStyle + ) { + self.name = name + self.swiftCallName = swiftCallName + self.explicitAccessControl = explicitAccessControl + self.cases = cases + self.rawType = rawType + self.namespace = namespace + self.emitStyle = emitStyle + } } -struct Effects: Codable { - var isAsync: Bool - var isThrows: Bool +public enum EnumType: String, Codable { + case simple // enum Direction { case north, south, east } + case rawValue // enum Mode: String { case light = "light" } + case associatedValue // enum Result { case success(String), failure(Int) } + case namespace // enum Utils { } (empty, used as namespace) } // MARK: - Exported Skeleton -struct ExportedFunction: Codable { - var name: String - var abiName: String - var parameters: [Parameter] - var returnType: BridgeType - var effects: Effects - var namespace: [String]? +public struct ExportedFunction: Codable { + public var name: String + public var abiName: String + public var parameters: [Parameter] + public var returnType: BridgeType + public var effects: Effects + public var namespace: [String]? + + public init( + name: String, + abiName: String, + parameters: [Parameter], + returnType: BridgeType, + effects: Effects, + namespace: [String]? = nil + ) { + self.name = name + self.abiName = abiName + self.parameters = parameters + self.returnType = returnType + self.effects = effects + self.namespace = namespace + } +} + +public struct ExportedClass: Codable { + public var name: String + public var swiftCallName: String + public var explicitAccessControl: String? + public var constructor: ExportedConstructor? + public var methods: [ExportedFunction] + public var properties: [ExportedProperty] + public var namespace: [String]? + + public init( + name: String, + swiftCallName: String, + explicitAccessControl: String?, + constructor: ExportedConstructor? = nil, + methods: [ExportedFunction], + properties: [ExportedProperty] = [], + namespace: [String]? = nil + ) { + self.name = name + self.swiftCallName = swiftCallName + self.explicitAccessControl = explicitAccessControl + self.constructor = constructor + self.methods = methods + self.properties = properties + self.namespace = namespace + } } -struct ExportedClass: Codable { - var name: String - var constructor: ExportedConstructor? - var methods: [ExportedFunction] - var namespace: [String]? +public struct ExportedConstructor: Codable { + public var abiName: String + public var parameters: [Parameter] + public var effects: Effects + public var namespace: [String]? + + public init(abiName: String, parameters: [Parameter], effects: Effects, namespace: [String]? = nil) { + self.abiName = abiName + self.parameters = parameters + self.effects = effects + self.namespace = namespace + } } -struct ExportedConstructor: Codable { - var abiName: String - var parameters: [Parameter] - var effects: Effects - var namespace: [String]? +public struct ExportedProperty: Codable { + public var name: String + public var type: BridgeType + public var isReadonly: Bool + + public init(name: String, type: BridgeType, isReadonly: Bool = false) { + self.name = name + self.type = type + self.isReadonly = isReadonly + } + + public func getterAbiName(className: String) -> String { + return "bjs_\(className)_\(name)_get" + } + + public func setterAbiName(className: String) -> String { + return "bjs_\(className)_\(name)_set" + } } -struct ExportedSkeleton: Codable { - let functions: [ExportedFunction] - let classes: [ExportedClass] +public struct ExportedSkeleton: Codable { + public let moduleName: String + public let functions: [ExportedFunction] + public let classes: [ExportedClass] + public let enums: [ExportedEnum] + + public init(moduleName: String, functions: [ExportedFunction], classes: [ExportedClass], enums: [ExportedEnum]) { + self.moduleName = moduleName + self.functions = functions + self.classes = classes + self.enums = enums + } } // MARK: - Imported Skeleton -struct ImportedFunctionSkeleton: Codable { - let name: String - let parameters: [Parameter] - let returnType: BridgeType - let documentation: String? +public struct ImportedFunctionSkeleton: Codable { + public let name: String + public let parameters: [Parameter] + public let returnType: BridgeType + public let documentation: String? - func abiName(context: ImportedTypeSkeleton?) -> String { + public func abiName(context: ImportedTypeSkeleton?) -> String { return context.map { "bjs_\($0.name)_\(name)" } ?? "bjs_\(name)" } } -struct ImportedConstructorSkeleton: Codable { - let parameters: [Parameter] +public struct ImportedConstructorSkeleton: Codable { + public let parameters: [Parameter] - func abiName(context: ImportedTypeSkeleton) -> String { + public func abiName(context: ImportedTypeSkeleton) -> String { return "bjs_\(context.name)_init" } } -struct ImportedPropertySkeleton: Codable { - let name: String - let isReadonly: Bool - let type: BridgeType - let documentation: String? +public struct ImportedPropertySkeleton: Codable { + public let name: String + public let isReadonly: Bool + public let type: BridgeType + public let documentation: String? - func getterAbiName(context: ImportedTypeSkeleton) -> String { + public func getterAbiName(context: ImportedTypeSkeleton) -> String { return "bjs_\(context.name)_\(name)_get" } - func setterAbiName(context: ImportedTypeSkeleton) -> String { + public func setterAbiName(context: ImportedTypeSkeleton) -> String { return "bjs_\(context.name)_\(name)_set" } } -struct ImportedTypeSkeleton: Codable { - let name: String - let constructor: ImportedConstructorSkeleton? - let methods: [ImportedFunctionSkeleton] - let properties: [ImportedPropertySkeleton] - let documentation: String? +public struct ImportedTypeSkeleton: Codable { + public let name: String + public let constructor: ImportedConstructorSkeleton? + public let methods: [ImportedFunctionSkeleton] + public let properties: [ImportedPropertySkeleton] + public let documentation: String? } -struct ImportedFileSkeleton: Codable { - let functions: [ImportedFunctionSkeleton] - let types: [ImportedTypeSkeleton] +public struct ImportedFileSkeleton: Codable { + public let functions: [ImportedFunctionSkeleton] + public let types: [ImportedTypeSkeleton] } -struct ImportedModuleSkeleton: Codable { - let moduleName: String - var children: [ImportedFileSkeleton] +public struct ImportedModuleSkeleton: Codable { + public let moduleName: String + public var children: [ImportedFileSkeleton] + + public init(moduleName: String, children: [ImportedFileSkeleton]) { + self.moduleName = moduleName + self.children = children + } } +// MARK: - BridgeType extension + extension BridgeType { - var abiReturnType: WasmCoreType? { + public var abiReturnType: WasmCoreType? { switch self { case .void: return nil case .bool: return .i32 @@ -118,6 +322,14 @@ extension BridgeType { case .swiftHeapObject: // UnsafeMutableRawPointer is returned as an i32 pointer return .pointer + case .caseEnum: + return .i32 + case .rawValueEnum(_, let rawType): + return rawType.wasmCoreType + case .associatedValueEnum: + return nil + case .namespaceEnum: + return nil } } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSCore b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSCore deleted file mode 120000 index c869f69c..00000000 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSCore +++ /dev/null @@ -1 +0,0 @@ -../BridgeJSCore \ No newline at end of file diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSSkeleton b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSSkeleton deleted file mode 120000 index a2c26678..00000000 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSSkeleton +++ /dev/null @@ -1 +0,0 @@ -../BridgeJSSkeleton \ No newline at end of file diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift index 6096e2b3..dba6fe47 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift @@ -3,11 +3,23 @@ @preconcurrency import var Foundation.stderr @preconcurrency import struct Foundation.URL @preconcurrency import struct Foundation.Data +@preconcurrency import struct Foundation.ObjCBool @preconcurrency import class Foundation.JSONEncoder @preconcurrency import class Foundation.FileManager @preconcurrency import class Foundation.JSONDecoder +@preconcurrency import class Foundation.ProcessInfo import SwiftParser +#if canImport(BridgeJSCore) +import BridgeJSCore +#endif +#if canImport(BridgeJSSkeleton) +import BridgeJSSkeleton +#endif +#if canImport(TS2Skeleton) +import TS2Skeleton +#endif + /// BridgeJS Tool /// /// A command-line tool to generate Swift-JavaScript bridge code for WebAssembly applications. @@ -40,7 +52,7 @@ import SwiftParser do { try run() } catch { - printStderr("Error: \(error)") + printStderr("error: \(error)") exit(1) } } @@ -73,6 +85,10 @@ import SwiftParser help: "Print verbose output", required: false ), + "target-dir": OptionRule( + help: "The SwiftPM package target directory", + required: true + ), "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", @@ -89,6 +105,9 @@ import SwiftParser ) let progress = ProgressReporting(verbose: doubleDashOptions["verbose"] == "true") var importer = ImportTS(progress: progress, moduleName: doubleDashOptions["module-name"]!) + let targetDirectory = URL(fileURLWithPath: doubleDashOptions["target-dir"]!) + let config = try BridgeJSConfig.load(targetDirectory: targetDirectory) + let nodePath: URL = try config.findTool("node", targetDirectory: targetDirectory) for inputFile in positionalArguments { if inputFile.hasSuffix(".json") { let sourceURL = URL(fileURLWithPath: inputFile) @@ -99,7 +118,7 @@ import SwiftParser importer.addSkeleton(skeleton) } else if inputFile.hasSuffix(".d.ts") { let tsconfigPath = URL(fileURLWithPath: doubleDashOptions["project"]!) - try importer.addSourceFile(inputFile, tsconfigPath: tsconfigPath.path) + try importer.addSourceFile(inputFile, tsconfigPath: tsconfigPath.path, nodePath: nodePath) } } @@ -139,6 +158,10 @@ import SwiftParser let parser = ArgumentParser( singleDashOptions: [:], doubleDashOptions: [ + "module-name": OptionRule( + help: "The name of the module for external function references", + required: true + ), "output-skeleton": OptionRule( help: "The output file path for the skeleton of the exported Swift APIs", required: true @@ -158,7 +181,7 @@ import SwiftParser arguments: Array(arguments.dropFirst()) ) let progress = ProgressReporting(verbose: doubleDashOptions["verbose"] == "true") - let exporter = ExportSwift(progress: progress) + let exporter = ExportSwift(progress: progress, moduleName: doubleDashOptions["module-name"]!) for inputFile in positionalArguments.sorted() { let sourceURL = URL(fileURLWithPath: inputFile) guard sourceURL.pathExtension == "swift" else { continue } diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/TS2Skeleton b/Plugins/BridgeJS/Sources/BridgeJSTool/TS2Skeleton deleted file mode 120000 index f9ba2f57..00000000 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/TS2Skeleton +++ /dev/null @@ -1 +0,0 @@ -../TS2Skeleton \ No newline at end of file diff --git a/Plugins/BridgeJS/Sources/BridgeJSUtilities/Utilities.swift b/Plugins/BridgeJS/Sources/BridgeJSUtilities/Utilities.swift new file mode 100644 index 00000000..f091e4a3 --- /dev/null +++ b/Plugins/BridgeJS/Sources/BridgeJSUtilities/Utilities.swift @@ -0,0 +1,6 @@ +extension String { + public var capitalizedFirstLetter: String { + guard !isEmpty else { return self } + return prefix(1).uppercased() + dropFirst() + } +} diff --git a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/cli.js b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/cli.js index f708082c..41f6e419 100644 --- a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/cli.js +++ b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/cli.js @@ -22,13 +22,13 @@ class DiagnosticEngine { getCurrentDirectory: () => ts.sys.getCurrentDirectory(), }; } - + /** * @param {readonly ts.Diagnostic[]} diagnostics */ tsDiagnose(diagnostics) { const message = ts.formatDiagnosticsWithColorAndContext(diagnostics, this.formattHost); - console.log(message); + process.stderr.write(message, "utf-8"); } static LEVELS = { diff --git a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/index.d.ts b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/index.d.ts index e1daa4af..b53e2420 100644 --- a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/index.d.ts +++ b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/index.d.ts @@ -12,10 +12,15 @@ export type Parameter = { type: BridgeType; } +export type Effects = { + isAsync: boolean; +} + export type ImportFunctionSkeleton = { name: string; parameters: Parameter[]; returnType: BridgeType; + effects: Effects; documentation: string | undefined; } diff --git a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/processor.js b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/processor.js index 0f97ea14..cdcff719 100644 --- a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/processor.js +++ b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/processor.js @@ -142,6 +142,10 @@ export class TypeProcessor { */ visitFunctionLikeDecl(node) { if (!node.name) return null; + const name = node.name.getText(); + if (!isValidSwiftDeclName(name)) { + return null; + } const signature = this.checker.getSignatureFromDeclaration(node); if (!signature) return null; @@ -158,10 +162,11 @@ export class TypeProcessor { const documentation = this.getFullJSDocText(node); return { - name: node.name.getText(), + name, parameters, returnType: bridgeReturnType, documentation, + effects: { isAsync: false }, }; } @@ -205,11 +210,17 @@ export class TypeProcessor { */ visitPropertyDecl(node) { if (!node.name) return null; + + const propertyName = node.name.getText(); + if (!isValidSwiftDeclName(propertyName)) { + return null; + } + const type = this.checker.getTypeAtLocation(node) const bridgeType = this.visitType(type, node); const isReadonly = node.modifiers?.some(m => m.kind === ts.SyntaxKind.ReadonlyKeyword) ?? false; const documentation = this.getFullJSDocText(node); - return { name: node.name.getText(), type: bridgeType, isReadonly, documentation }; + return { name: propertyName, type: bridgeType, isReadonly, documentation }; } /** @@ -224,7 +235,7 @@ export class TypeProcessor { } /** - * @param {ts.ClassDeclaration} node + * @param {ts.ClassDeclaration} node * @returns {ImportTypeSkeleton | null} */ visitClassDecl(node) { @@ -341,6 +352,10 @@ export class TypeProcessor { * @private */ visitType(type, node) { + // Treat A and A as the same type + if (isTypeReference(type)) { + type = type.target; + } const maybeProcessed = this.processedTypes.get(type); if (maybeProcessed) { return maybeProcessed; @@ -364,8 +379,13 @@ export class TypeProcessor { "object": { "jsObject": {} }, "symbol": { "jsObject": {} }, "never": { "void": {} }, + "Promise": { + "jsObject": { + "_0": "JSPromise" + } + }, }; - const typeString = this.checker.typeToString(type); + const typeString = type.getSymbol()?.name ?? this.checker.typeToString(type); if (typeMap[typeString]) { return typeMap[typeString]; } @@ -377,7 +397,7 @@ export class TypeProcessor { if (this.checker.isTypeAssignableTo(type, this.checker.getStringType())) { return { "string": {} }; } - if (type.getFlags() & ts.TypeFlags.TypeParameter) { + if (type.isTypeParameter()) { return { "jsObject": {} }; } @@ -412,3 +432,36 @@ export class TypeProcessor { return undefined; } } + +/** + * @param {ts.Type} type + * @returns {type is ts.ObjectType} + */ +function isObjectType(type) { + // @ts-ignore + return typeof type.objectFlags === "number"; +} + +/** + * + * @param {ts.Type} type + * @returns {type is ts.TypeReference} + */ +function isTypeReference(type) { + return ( + isObjectType(type) && + (type.objectFlags & ts.ObjectFlags.Reference) !== 0 + ); +} + +/** + * Check if a declaration name is valid for Swift generation + * @param {string} name - Declaration name to check + * @returns {boolean} True if the name is valid for Swift + * @private + */ +export function isValidSwiftDeclName(name) { + // https://docs.swift.org/swift-book/documentation/the-swift-programming-language/lexicalstructure/ + const swiftIdentifierRegex = /^[_\p{ID_Start}][\p{ID_Continue}\u{200C}\u{200D}]*$/u; + return swiftIdentifierRegex.test(name); +} diff --git a/Plugins/BridgeJS/Sources/TS2Skeleton/TS2Skeleton.swift b/Plugins/BridgeJS/Sources/TS2Skeleton/TS2Skeleton.swift index 262393c4..c7725faf 100644 --- a/Plugins/BridgeJS/Sources/TS2Skeleton/TS2Skeleton.swift +++ b/Plugins/BridgeJS/Sources/TS2Skeleton/TS2Skeleton.swift @@ -12,7 +12,17 @@ import protocol Dispatch.DispatchSourceSignal import class Dispatch.DispatchSource -internal func which(_ executable: String) throws -> URL { +#if canImport(BridgeJSCore) +import BridgeJSCore +#endif +#if canImport(BridgeJSSkeleton) +import BridgeJSSkeleton +#endif + +internal func which( + _ executable: String, + environment: [String: String] = ProcessInfo.processInfo.environment +) -> URL? { func checkCandidate(_ candidate: URL) -> Bool { var isDirectory: ObjCBool = false let fileExists = FileManager.default.fileExists(atPath: candidate.path, isDirectory: &isDirectory) @@ -20,9 +30,9 @@ internal func which(_ executable: String) throws -> URL { } do { // Check overriding environment variable - let envVariable = executable.uppercased().replacingOccurrences(of: "-", with: "_") + "_PATH" - if let path = ProcessInfo.processInfo.environment[envVariable] { - let url = URL(fileURLWithPath: path).appendingPathComponent(executable) + let envVariable = "JAVASCRIPTKIT_" + executable.uppercased().replacingOccurrences(of: "-", with: "_") + "_EXEC" + if let executablePath = environment[envVariable] { + let url = URL(fileURLWithPath: executablePath) if checkCandidate(url) { return url } @@ -34,20 +44,45 @@ internal func which(_ executable: String) throws -> URL { #else pathSeparator = ":" #endif - let paths = ProcessInfo.processInfo.environment["PATH"]!.split(separator: pathSeparator) + let paths = environment["PATH"]?.split(separator: pathSeparator) ?? [] for path in paths { let url = URL(fileURLWithPath: String(path)).appendingPathComponent(executable) if checkCandidate(url) { return url } } - throw BridgeJSCoreError("Executable \(executable) not found in PATH") + return nil +} + +extension BridgeJSConfig { + /// Find a tool from the system PATH, using environment variable override, or bridge-js.config.json + public func findTool(_ name: String, targetDirectory: URL) throws -> URL { + if let tool = tools?[name] { + return URL(fileURLWithPath: tool) + } + if let url = which(name) { + return url + } + + // Emit a helpful error message with a suggestion to create a local config override. + throw BridgeJSCoreError( + """ + Executable "\(name)" not found in PATH. \ + Hint: Try setting the JAVASCRIPTKIT_\(name.uppercased().replacingOccurrences(of: "-", with: "_"))_EXEC environment variable, \ + or create a local config override with: + echo '{ "tools": { "\(name)": "'$(which \(name))'" } }' > \(targetDirectory.appendingPathComponent("bridge-js.config.local.json").path) + """ + ) + } } extension ImportTS { /// Processes a TypeScript definition file and extracts its API information - mutating func addSourceFile(_ sourceFile: String, tsconfigPath: String) throws { - let nodePath = try which("node") + public mutating func addSourceFile( + _ sourceFile: String, + tsconfigPath: String, + nodePath: URL + ) throws { let ts2skeletonPath = URL(fileURLWithPath: #filePath) .deletingLastPathComponent() .appendingPathComponent("JavaScript") diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSCore b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSCore deleted file mode 120000 index 852d5b95..00000000 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSCore +++ /dev/null @@ -1 +0,0 @@ -../../Sources/BridgeJSCore \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLink b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLink deleted file mode 120000 index 94a1e954..00000000 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLink +++ /dev/null @@ -1 +0,0 @@ -../../Sources/BridgeJSLink \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift index 3432551b..37edf830 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift @@ -2,6 +2,9 @@ import Foundation import SwiftSyntax import SwiftParser import Testing +@testable import BridgeJSLink +@testable import BridgeJSCore +@testable import TS2Skeleton @Suite struct BridgeJSLinkTests { private func snapshot( @@ -44,7 +47,7 @@ import Testing func snapshotExport(input: String) throws { let url = Self.inputsDirectory.appendingPathComponent(input) let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8)) - let swiftAPI = ExportSwift(progress: .silent) + let swiftAPI = ExportSwift(progress: .silent, moduleName: "TestModule") try swiftAPI.addSourceFile(sourceFile, input) let name = url.deletingPathExtension().lastPathComponent @@ -63,7 +66,8 @@ import Testing let tsconfigPath = url.deletingLastPathComponent().appendingPathComponent("tsconfig.json") var importTS = ImportTS(progress: .silent, moduleName: "TestModule") - try importTS.addSourceFile(url.path, tsconfigPath: tsconfigPath.path) + let nodePath = try #require(which("node")) + try importTS.addSourceFile(url.path, tsconfigPath: tsconfigPath.path, nodePath: nodePath) let name = url.deletingPathExtension().deletingPathExtension().lastPathComponent let encoder = JSONEncoder() diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSSkeleton b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSSkeleton deleted file mode 120000 index c2cf2864..00000000 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSSkeleton +++ /dev/null @@ -1 +0,0 @@ -../../Sources/BridgeJSSkeleton \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/ExportSwiftTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/ExportSwiftTests.swift index 626248a7..e184116f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/ExportSwiftTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/ExportSwiftTests.swift @@ -3,6 +3,8 @@ import SwiftSyntax import SwiftParser import Testing +@testable import BridgeJSCore + @Suite struct ExportSwiftTests { private func snapshot( swiftAPI: ExportSwift, @@ -45,7 +47,7 @@ import Testing @Test(arguments: collectInputs()) func snapshot(input: String) throws { - let swiftAPI = ExportSwift(progress: .silent) + let swiftAPI = ExportSwift(progress: .silent, moduleName: "TestModule") let url = Self.inputsDirectory.appendingPathComponent(input) let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8)) try swiftAPI.addSourceFile(sourceFile, input) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/ImportTSTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/ImportTSTests.swift index 071c3d1d..ef642ed3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/ImportTSTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/ImportTSTests.swift @@ -1,5 +1,7 @@ import Testing import Foundation +@testable import BridgeJSCore +@testable import TS2Skeleton @Suite struct ImportTSTests { static let inputsDirectory = URL(fileURLWithPath: #filePath).deletingLastPathComponent().appendingPathComponent( @@ -16,8 +18,9 @@ import Foundation func snapshot(input: String) throws { var api = ImportTS(progress: .silent, moduleName: "Check") let url = Self.inputsDirectory.appendingPathComponent(input) + let nodePath = try #require(which("node")) let tsconfigPath = url.deletingLastPathComponent().appendingPathComponent("tsconfig.json") - try api.addSourceFile(url.path, tsconfigPath: tsconfigPath.path) + try api.addSourceFile(url.path, tsconfigPath: tsconfigPath.path, nodePath: nodePath) let outputSwift = try #require(try api.finalize()) let name = url.deletingPathExtension().deletingPathExtension().deletingPathExtension().lastPathComponent try assertSnapshot( diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.d.ts new file mode 100644 index 00000000..cb0d0cef --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.d.ts @@ -0,0 +1,7 @@ +export function asyncReturnVoid(): Promise; +export function asyncRoundTripInt(v: number): Promise; +export function asyncRoundTripString(v: string): Promise; +export function asyncRoundTripBool(v: boolean): Promise; +export function asyncRoundTripFloat(v: number): Promise; +export function asyncRoundTripDouble(v: number): Promise; +export function asyncRoundTripJSObject(v: any): Promise; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.swift new file mode 100644 index 00000000..214331b3 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.swift @@ -0,0 +1,19 @@ +@JS func asyncReturnVoid() async {} +@JS func asyncRoundTripInt(_ v: Int) async -> Int { + return v +} +@JS func asyncRoundTripString(_ v: String) async -> String { + return v +} +@JS func asyncRoundTripBool(_ v: Bool) async -> Bool { + return v +} +@JS func asyncRoundTripFloat(_ v: Float) async -> Float { + return v +} +@JS func asyncRoundTripDouble(_ v: Double) async -> Double { + return v +} +@JS func asyncRoundTripJSObject(_ v: JSObject) async -> JSObject { + return v +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumCase.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumCase.swift new file mode 100644 index 00000000..15bdb9f4 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumCase.swift @@ -0,0 +1,30 @@ +@JS enum Direction { + case north + case south + case east + case west +} + +@JS enum Status { + case loading + case success + case error +} + +@JS func setDirection(_ direction: Direction) +@JS func getDirection() -> Direction +@JS func processDirection(_ input: Direction) -> Status + +@JS(enumStyle: .tsEnum) enum TSDirection { + case north + case south + case east + case west +} + +@JS func setTSDirection(_ direction: TSDirection) +@JS func getTSDirection() -> TSDirection + +@JS public enum PublicStatus { + case success +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumNamespace.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumNamespace.swift new file mode 100644 index 00000000..26a4e9c3 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumNamespace.swift @@ -0,0 +1,56 @@ +// Empty enums to act as namespace wrappers for nested elements + +@JS enum Utils { + @JS class Converter { + @JS init() {} + + @JS func toString(value: Int) -> String { + return String(value) + } + } +} + +@JS enum Networking { + @JS enum API { + @JS enum Method { + case get + case post + case put + case delete + } + // Invalid to declare @JS(namespace) here as it would be conflicting with nesting + @JS class HTTPServer { + @JS init() {} + @JS func call(_ method: Method) {} + } + } +} + +@JS enum Configuration { + @JS enum LogLevel: String { + case debug = "debug" + case info = "info" + case warning = "warning" + case error = "error" + } + + @JS enum Port: Int { + case http = 80 + case https = 443 + case development = 3000 + } +} + +@JS(namespace: "Networking.APIV2") +enum Internal { + @JS enum SupportedMethod { + case get + case post + } + @JS class TestServer { + @JS init() {} + @JS func call(_ method: SupportedMethod) {} + } +} + +// TODO: Add namespace enum with static functions when supported diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumRawType.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumRawType.swift new file mode 100644 index 00000000..799df164 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumRawType.swift @@ -0,0 +1,117 @@ +@JS enum Theme: String { + case light = "light" + case dark = "dark" + case auto = "auto" +} + +@JS(enumStyle: .tsEnum) enum TSTheme: String { + case light = "light" + case dark = "dark" + case auto = "auto" +} + +@JS enum FeatureFlag: Bool { + case enabled = true + case disabled = false +} + +@JS enum HttpStatus: Int { + case ok = 200 + case notFound = 404 + case serverError = 500 +} + +@JS(enumStyle: .tsEnum) enum TSHttpStatus: Int { + case ok = 200 + case notFound = 404 + case serverError = 500 +} + +@JS enum Priority: Int32 { + case lowest = 1 + case low = 2 + case medium = 3 + case high = 4 + case highest = 5 +} + +@JS enum FileSize: Int64 { + case tiny = 1024 + case small = 10240 + case medium = 102400 + case large = 1048576 +} + +@JS enum UserId: UInt { + case guest = 0 + case user = 1000 + case admin = 9999 +} + +@JS enum TokenId: UInt32 { + case invalid = 0 + case session = 12345 + case refresh = 67890 +} + +@JS enum SessionId: UInt64 { + case none = 0 + case active = 9876543210 + case expired = 1234567890 +} + +@JS enum Precision: Float { + case rough = 0.1 + case normal = 0.01 + case fine = 0.001 +} + +@JS enum Ratio: Double { + case quarter = 0.25 + case half = 0.5 + case golden = 1.618 + case pi = 3.14159 +} + +@JS func setTheme(_ theme: Theme) +@JS func getTheme() -> Theme + +@JS func setTSTheme(_ theme: TSTheme) +@JS func getTSTheme() -> TSTheme + +@JS func setFeatureFlag(_ flag: FeatureFlag) +@JS func getFeatureFlag() -> FeatureFlag + +@JS func setHttpStatus(_ status: HttpStatus) +@JS func getHttpStatus() -> HttpStatus + +@JS func setTSHttpStatus(_ status: TSHttpStatus) +@JS func getTSHttpStatus() -> TSHttpStatus + +@JS func setPriority(_ priority: Priority) +@JS func getPriority() -> Priority + +@JS func setFileSize(_ size: FileSize) +@JS func getFileSize() -> FileSize + +@JS func setUserId(_ id: UserId) +@JS func getUserId() -> UserId + +@JS func setTokenId(_ token: TokenId) +@JS func getTokenId() -> TokenId + +@JS func setSessionId(_ session: SessionId) +@JS func getSessionId() -> SessionId + +@JS func setPrecision(_ precision: Precision) +@JS func getPrecision() -> Precision + +@JS func setRatio(_ ratio: Ratio) +@JS func getRatio() -> Ratio + +@JS func setFeatureFlag(_ featureFlag: FeatureFlag) +@JS func getFeatureFlag() -> FeatureFlag + +@JS func processTheme(_ theme: Theme) -> HttpStatus +@JS func convertPriority(_ status: HttpStatus) -> Priority +@JS func validateSession(_ session: SessionId) -> Theme diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/InvalidPropertyNames.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/InvalidPropertyNames.d.ts new file mode 100644 index 00000000..d21f3c20 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/InvalidPropertyNames.d.ts @@ -0,0 +1,23 @@ +interface ArrayBufferLike { + readonly byteLength: number; + readonly [Symbol.toStringTag]: string; + slice(begin: number, end: number): ArrayBufferLike; +} + +interface WeirdNaming { + normalProperty: string; + "property-with-dashes": number; + "123invalidStart": boolean; + "property with spaces": string; + readonly [Symbol.species]: any; + [Symbol.asyncIterator](): AsyncIterator; + "@specialChar": number; + "constructor": string; // This should be valid + for: string; + Any: string; + as(): void; + "try"(): void; +} + +export function createArrayBuffer(): ArrayBufferLike; +export function createWeirdObject(): WeirdNaming; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MultipleImportedTypes.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MultipleImportedTypes.d.ts new file mode 100644 index 00000000..70dc2b72 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MultipleImportedTypes.d.ts @@ -0,0 +1,23 @@ +// Test case for multiple imported types with methods and properties +export interface DatabaseConnection { + connect(url: string): void; + execute(query: string): any; + readonly isConnected: boolean; + connectionTimeout: number; +} + +export interface Logger { + log(message: string): void; + error(message: string, error: any): void; + readonly level: string; +} + +export interface ConfigManager { + get(key: string): any; + set(key: string, value: any): void; + readonly configPath: string; +} + +export function createDatabaseConnection(config: any): DatabaseConnection; +export function createLogger(level: string): Logger; +export function getConfigManager(): ConfigManager; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/PropertyTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/PropertyTypes.swift new file mode 100644 index 00000000..68482501 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/PropertyTypes.swift @@ -0,0 +1,97 @@ +@JS class PropertyHolder { + // Primitive properties + @JS var intValue: Int + @JS var floatValue: Float + @JS var doubleValue: Double + @JS var boolValue: Bool + @JS var stringValue: String + + // Readonly primitive properties + @JS let readonlyInt: Int = 42 + @JS let readonlyFloat: Float = 3.14 + @JS let readonlyDouble: Double = 2.718281828 + @JS let readonlyBool: Bool = true + @JS let readonlyString: String = "constant" + + // JSObject property + @JS var jsObject: JSObject + + // SwiftHeapObject property (will be set later) + @JS var sibling: PropertyHolder + + // Lazy stored property - should this be supported or generate an error? + @JS lazy var lazyValue: String = "computed lazily" + + // Computed property with getter only (readonly) + @JS var computedReadonly: Int { + return intValue * 2 + } + + // Computed property with getter and setter + @JS var computedReadWrite: String { + get { + return "Value: \(intValue)" + } + set { + // Parse the number from "Value: X" format + if let range = newValue.range(of: "Value: "), + let number = Int(String(newValue[range.upperBound...])) + { + intValue = number + } + } + } + + // Property with property observers + @JS var observedProperty: Int { + willSet { + print("Will set to \(newValue)") + } + didSet { + print("Did set from \(oldValue)") + } + } + + @JS init( + intValue: Int, + floatValue: Float, + doubleValue: Double, + boolValue: Bool, + stringValue: String, + jsObject: JSObject + ) { + self.intValue = intValue + self.floatValue = floatValue + self.doubleValue = doubleValue + self.boolValue = boolValue + self.stringValue = stringValue + self.jsObject = jsObject + self.sibling = self + } + + @JS func getAllValues() -> String { + return "int:\(intValue),float:\(floatValue),double:\(doubleValue),bool:\(boolValue),string:\(stringValue)" + } +} + +@JS func createPropertyHolder( + intValue: Int, + floatValue: Float, + doubleValue: Double, + boolValue: Bool, + stringValue: String, + jsObject: JSObject +) -> PropertyHolder { + return PropertyHolder( + intValue: intValue, + floatValue: floatValue, + doubleValue: doubleValue, + boolValue: boolValue, + stringValue: stringValue, + jsObject: jsObject + ) +} + +@JS func testPropertyHolder(holder: PropertyHolder) -> String { + return holder.getAllValues() +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftClass.swift index a803504f..116b0087 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftClass.swift @@ -1,5 +1,5 @@ @JS class Greeter { - var name: String + @JS var name: String @JS init(name: String) { self.name = name @@ -15,3 +15,6 @@ @JS func takeGreeter(greeter: Greeter) { print(greeter.greet()) } + +@JS public class PublicGreeter {} +@JS package class PackageGreeter {} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/TS2SkeletonLike.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/TS2SkeletonLike.d.ts new file mode 100644 index 00000000..d3f07d7a --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/TS2SkeletonLike.d.ts @@ -0,0 +1,14 @@ +// Test case similar to the TS2Skeleton use case that caused the original bug +export interface TypeScriptProcessor { + convert(ts: string): string; + validate(ts: string): boolean; + readonly version: string; +} + +export interface CodeGenerator { + generate(input: any): string; + readonly outputFormat: string; +} + +export function createTS2Skeleton(): TypeScriptProcessor; +export function createCodeGenerator(format: string): CodeGenerator; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/tsconfig.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/tsconfig.json new file mode 100644 index 00000000..86e2ba17 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2017", + "module": "commonjs", + "lib": ["es2017"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/TS2Skeleton b/Plugins/BridgeJS/Tests/BridgeJSToolTests/TS2Skeleton deleted file mode 120000 index feba8470..00000000 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/TS2Skeleton +++ /dev/null @@ -1 +0,0 @@ -../../Sources/TS2Skeleton \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/WhichTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/WhichTests.swift new file mode 100644 index 00000000..958d4d64 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/WhichTests.swift @@ -0,0 +1,178 @@ +import Testing +import Foundation +@testable import TS2Skeleton +@testable import BridgeJSCore + +@Suite struct WhichTests { + + // MARK: - Helper Functions + + private static var pathSeparator: String { + #if os(Windows) + return ";" + #else + return ":" + #endif + } + + // MARK: - Successful Path Resolution Tests + + @Test func whichFindsExecutableInPath() throws { + try withTemporaryDirectory { tempDir, _ in + let execFile = tempDir.appendingPathComponent("testexec") + try "#!/bin/sh\necho 'test'".write(to: execFile, atomically: true, encoding: .utf8) + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: execFile.path) + + let environment = ["PATH": tempDir.path] + + let result = try #require(which("testexec", environment: environment)) + + #expect(result.path == execFile.path) + } + } + + @Test func whichReturnsFirstMatchInPath() throws { + try withTemporaryDirectory { tempDir1, _ in + try withTemporaryDirectory { tempDir2, _ in + let exec1 = tempDir1.appendingPathComponent("testexec") + let exec2 = tempDir2.appendingPathComponent("testexec") + + // Create executable files in both directories + try "#!/bin/sh\necho 'first'".write(to: exec1, atomically: true, encoding: .utf8) + try "#!/bin/sh\necho 'second'".write(to: exec2, atomically: true, encoding: .utf8) + + // Make files executable + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: exec1.path) + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: exec2.path) + + let pathEnv = "\(tempDir1.path)\(Self.pathSeparator)\(tempDir2.path)" + let environment = ["PATH": pathEnv] + + let result = try #require(which("testexec", environment: environment)) + + // Should return the first one found + #expect(result.path == exec1.path) + } + } + } + + // MARK: - Environment Variable Override Tests + + @Test func whichUsesEnvironmentVariableOverride() throws { + try withTemporaryDirectory { tempDir, _ in + let customExec = tempDir.appendingPathComponent("mynode") + try "#!/bin/sh\necho 'custom node'".write(to: customExec, atomically: true, encoding: .utf8) + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: customExec.path) + + let environment = [ + "PATH": "/nonexistent/path", + "JAVASCRIPTKIT_NODE_EXEC": customExec.path, + ] + + let result = try #require(which("node", environment: environment)) + + #expect(result.path == customExec.path) + } + } + + @Test func whichHandlesHyphenatedExecutableNames() throws { + try withTemporaryDirectory { tempDir, _ in + let customExec = tempDir.appendingPathComponent("my-exec") + try "#!/bin/sh\necho 'hyphenated'".write(to: customExec, atomically: true, encoding: .utf8) + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: customExec.path) + + let environment = [ + "PATH": "/nonexistent/path", + "JAVASCRIPTKIT_MY_EXEC_EXEC": customExec.path, + ] + + let result = try #require(which("my-exec", environment: environment)) + + #expect(result.path == customExec.path) + } + } + + @Test func whichPrefersEnvironmentOverridePath() throws { + try withTemporaryDirectory { tempDir1, _ in + try withTemporaryDirectory { tempDir2, _ in + let pathExec = tempDir1.appendingPathComponent("testexec") + let envExec = tempDir2.appendingPathComponent("testexec") + + try "#!/bin/sh\necho 'from path'".write(to: pathExec, atomically: true, encoding: .utf8) + try "#!/bin/sh\necho 'from env'".write(to: envExec, atomically: true, encoding: .utf8) + + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: pathExec.path) + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: envExec.path) + + let environment = [ + "PATH": tempDir1.path, + "JAVASCRIPTKIT_TESTEXEC_EXEC": envExec.path, + ] + + let result = try #require(which("testexec", environment: environment)) + + // Should prefer environment variable over PATH + #expect(result.path == envExec.path) + } + } + } + + // MARK: - Error Handling Tests + + @Test func whichThrowsWhenExecutableNotFound() throws { + let environment = ["PATH": "/nonexistent\(Self.pathSeparator)/also/nonexistent"] + + #expect(which("nonexistent_executable_12345", environment: environment) == nil) + } + + @Test func whichThrowsWhenEnvironmentPathIsInvalid() throws { + try withTemporaryDirectory { tempDir, _ in + let nonExecFile = tempDir.appendingPathComponent("notexecutable") + try "not executable".write(to: nonExecFile, atomically: true, encoding: .utf8) + + let environment = [ + "PATH": tempDir.path, + "JAVASCRIPTKIT_NOTEXECUTABLE_EXEC": nonExecFile.path, + ] + + #expect(which("notexecutable", environment: environment) == nil) + } + } + + @Test func whichThrowsWhenPathPointsToDirectory() throws { + try withTemporaryDirectory { tempDir, _ in + let environment = [ + "PATH": "/nonexistent/path", + "JAVASCRIPTKIT_TESTEXEC_EXEC": tempDir.path, + ] + + #expect(which("testexec", environment: environment) == nil) + } + } + + // MARK: - Edge Case Tests + + @Test func whichHandlesEmptyPath() throws { + let environment = ["PATH": ""] + + #expect(which("anyexec", environment: environment) == nil) + } + + @Test func whichHandlesMissingPathEnvironment() throws { + let environment: [String: String] = [:] + + #expect(which("anyexec", environment: environment) == nil) + } + + @Test func whichIgnoresNonExecutableFiles() throws { + try withTemporaryDirectory { tempDir, _ in + let nonExecFile = tempDir.appendingPathComponent("testfile") + try "content".write(to: nonExecFile, atomically: true, encoding: .utf8) + // Don't set executable permissions + + let environment = ["PATH": tempDir.path] + + #expect(which("testfile", environment: environment) == nil) + } + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js index c90b3919..c122f179 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -46,24 +49,25 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_checkArray"] = function bjs_checkArray(a) { try { - options.imports.checkArray(swift.memory.getObject(a)); + imports.checkArray(swift.memory.getObject(a)); } catch (error) { setException(error); } } TestModule["bjs_checkArrayWithLength"] = function bjs_checkArrayWithLength(a, b) { try { - options.imports.checkArrayWithLength(swift.memory.getObject(a), b); + imports.checkArrayWithLength(swift.memory.getObject(a), b); } catch (error) { setException(error); } } TestModule["bjs_checkArray"] = function bjs_checkArray(a) { try { - options.imports.checkArray(swift.memory.getObject(a)); + imports.checkArray(swift.memory.getObject(a)); } catch (error) { setException(error); } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.d.ts new file mode 100644 index 00000000..aecab090 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.d.ts @@ -0,0 +1,24 @@ +// 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`. + +export type Exports = { + asyncReturnVoid(): Promise; + asyncRoundTripInt(v: number): Promise; + asyncRoundTripString(v: string): Promise; + asyncRoundTripBool(v: boolean): Promise; + asyncRoundTripFloat(v: number): Promise; + asyncRoundTripDouble(v: number): Promise; + asyncRoundTripJSObject(v: any): Promise; +} +export type Imports = { +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js new file mode 100644 index 00000000..97c1e215 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js @@ -0,0 +1,115 @@ +// 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`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + const bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + + + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + + return { + asyncReturnVoid: function bjs_asyncReturnVoid() { + const ret = instance.exports.bjs_asyncReturnVoid(); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + return ret1; + }, + asyncRoundTripInt: function bjs_asyncRoundTripInt(v) { + const ret = instance.exports.bjs_asyncRoundTripInt(v); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + return ret1; + }, + asyncRoundTripString: function bjs_asyncRoundTripString(v) { + const vBytes = textEncoder.encode(v); + const vId = swift.memory.retain(vBytes); + const ret = instance.exports.bjs_asyncRoundTripString(vId, vBytes.length); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + swift.memory.release(vId); + return ret1; + }, + asyncRoundTripBool: function bjs_asyncRoundTripBool(v) { + const ret = instance.exports.bjs_asyncRoundTripBool(v); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + return ret1; + }, + asyncRoundTripFloat: function bjs_asyncRoundTripFloat(v) { + const ret = instance.exports.bjs_asyncRoundTripFloat(v); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + return ret1; + }, + asyncRoundTripDouble: function bjs_asyncRoundTripDouble(v) { + const ret = instance.exports.bjs_asyncRoundTripDouble(v); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + return ret1; + }, + asyncRoundTripJSObject: function bjs_asyncRoundTripJSObject(v) { + const ret = instance.exports.bjs_asyncRoundTripJSObject(swift.memory.retain(v)); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + return ret1; + }, + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.d.ts new file mode 100644 index 00000000..dea0bd18 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.d.ts @@ -0,0 +1,24 @@ +// 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`. + +export type Exports = { +} +export type Imports = { + asyncReturnVoid(): JSPromise; + asyncRoundTripInt(v: number): JSPromise; + asyncRoundTripString(v: string): JSPromise; + asyncRoundTripBool(v: boolean): JSPromise; + asyncRoundTripFloat(v: number): JSPromise; + asyncRoundTripDouble(v: number): JSPromise; + asyncRoundTripJSObject(v: any): JSPromise; +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js new file mode 100644 index 00000000..0b07aedd --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js @@ -0,0 +1,136 @@ +// 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`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + const bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + 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"] = importObject["TestModule"] || {}; + TestModule["bjs_asyncReturnVoid"] = function bjs_asyncReturnVoid() { + try { + let ret = imports.asyncReturnVoid(); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_asyncRoundTripInt"] = function bjs_asyncRoundTripInt(v) { + try { + let ret = imports.asyncRoundTripInt(v); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_asyncRoundTripString"] = function bjs_asyncRoundTripString(v) { + try { + const vObject = swift.memory.getObject(v); + swift.memory.release(v); + let ret = imports.asyncRoundTripString(vObject); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_asyncRoundTripBool"] = function bjs_asyncRoundTripBool(v) { + try { + let ret = imports.asyncRoundTripBool(v !== 0); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_asyncRoundTripFloat"] = function bjs_asyncRoundTripFloat(v) { + try { + let ret = imports.asyncRoundTripFloat(v); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_asyncRoundTripDouble"] = function bjs_asyncRoundTripDouble(v) { + try { + let ret = imports.asyncRoundTripDouble(v); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_asyncRoundTripJSObject"] = function bjs_asyncRoundTripJSObject(v) { + try { + let ret = imports.asyncRoundTripJSObject(swift.memory.getObject(v)); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + + return { + + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.d.ts new file mode 100644 index 00000000..1375cc36 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.d.ts @@ -0,0 +1,49 @@ +// 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`. + +export const Direction: { + readonly North: 0; + readonly South: 1; + readonly East: 2; + readonly West: 3; +}; +export type Direction = typeof Direction[keyof typeof Direction]; + +export const Status: { + readonly Loading: 0; + readonly Success: 1; + readonly Error: 2; +}; +export type Status = typeof Status[keyof typeof Status]; + +export enum TSDirection { + North = 0, + South = 1, + East = 2, + West = 3, +} + +export const PublicStatus: { + readonly Success: 0; +}; +export type PublicStatus = typeof PublicStatus[keyof typeof PublicStatus]; + +export type Exports = { + setDirection(direction: Direction): void; + getDirection(): Direction; + processDirection(input: Direction): Status; + setTSDirection(direction: TSDirection): void; + getTSDirection(): TSDirection; +} +export type Imports = { +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js new file mode 100644 index 00000000..b5bc1145 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js @@ -0,0 +1,113 @@ +// 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`. + +export const Direction = { + North: 0, + South: 1, + East: 2, + West: 3, +}; + +export const Status = { + Loading: 0, + Success: 1, + Error: 2, +}; + +export const TSDirection = { + North: 0, + South: 1, + East: 2, + West: 3, +}; + +export const PublicStatus = { + Success: 0, +}; + + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + const bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + + + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + + return { + setDirection: function bjs_setDirection(direction) { + instance.exports.bjs_setDirection(direction); + }, + getDirection: function bjs_getDirection() { + const ret = instance.exports.bjs_getDirection(); + return ret; + }, + processDirection: function bjs_processDirection(input) { + const ret = instance.exports.bjs_processDirection(input); + return ret; + }, + setTSDirection: function bjs_setTSDirection(direction) { + instance.exports.bjs_setTSDirection(direction); + }, + getTSDirection: function bjs_getTSDirection() { + const ret = instance.exports.bjs_getTSDirection(); + return ret; + }, + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.d.ts new file mode 100644 index 00000000..3d37ca6c --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.d.ts @@ -0,0 +1,96 @@ +// 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`. + +export {}; + +declare global { + namespace Configuration { + const LogLevel: { + readonly Debug: "debug"; + readonly Info: "info"; + readonly Warning: "warning"; + readonly Error: "error"; + }; + type LogLevel = typeof LogLevel[keyof typeof LogLevel]; + const Port: { + readonly Http: 80; + readonly Https: 443; + readonly Development: 3000; + }; + type Port = typeof Port[keyof typeof Port]; + } + namespace Networking { + namespace API { + class HTTPServer { + constructor(); + call(method: Networking.API.Method): void; + } + const Method: { + readonly Get: 0; + readonly Post: 1; + readonly Put: 2; + readonly Delete: 3; + }; + type Method = typeof Method[keyof typeof Method]; + } + namespace APIV2 { + namespace Internal { + class TestServer { + constructor(); + call(method: Internal.SupportedMethod): void; + } + const SupportedMethod: { + readonly Get: 0; + readonly Post: 1; + }; + type SupportedMethod = typeof SupportedMethod[keyof typeof SupportedMethod]; + } + } + } + namespace Utils { + class Converter { + constructor(); + toString(value: number): string; + } + } +} + +/// Represents a Swift heap object like a class instance or an actor instance. +export interface SwiftHeapObject { + /// Release the heap object. + /// + /// Note: Calling this method will release the heap object and it will no longer be accessible. + release(): void; +} +export interface Converter extends SwiftHeapObject { + toString(value: number): string; +} +export interface HTTPServer extends SwiftHeapObject { + call(method: Networking.API.Method): void; +} +export interface TestServer extends SwiftHeapObject { + call(method: Internal.SupportedMethod): void; +} +export type Exports = { + Converter: { + new(): Converter; + } + HTTPServer: { + new(): HTTPServer; + } + TestServer: { + new(): TestServer; + } +} +export type Imports = { +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js new file mode 100644 index 00000000..677e02c9 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js @@ -0,0 +1,219 @@ +// 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`. + +export const Method = { + Get: 0, + Post: 1, + Put: 2, + Delete: 3, +}; + +export const LogLevel = { + Debug: "debug", + Info: "info", + Warning: "warning", + Error: "error", +}; + +export const Port = { + Http: 80, + Https: 443, + Development: 3000, +}; + +export const SupportedMethod = { + Get: 0, + Post: 1, +}; + + +if (typeof globalThis.Networking === 'undefined') { + globalThis.Networking = {}; +} +if (typeof globalThis.Networking.API === 'undefined') { + globalThis.Networking.API = {}; +} +if (typeof globalThis.Configuration === 'undefined') { + globalThis.Configuration = {}; +} +if (typeof globalThis.Networking.APIV2 === 'undefined') { + globalThis.Networking.APIV2 = {}; +} +if (typeof globalThis.Networking.APIV2.Internal === 'undefined') { + globalThis.Networking.APIV2.Internal = {}; +} + +globalThis.Networking.API.Method = Method; +globalThis.Configuration.LogLevel = LogLevel; +globalThis.Configuration.Port = Port; +globalThis.Networking.APIV2.Internal.SupportedMethod = SupportedMethod; + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + const bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + // Wrapper functions for module: TestModule + if (!importObject["TestModule"]) { + importObject["TestModule"] = {}; + } + importObject["TestModule"]["bjs_Converter_wrap"] = function(pointer) { + const obj = Converter.__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_HTTPServer_wrap"] = function(pointer) { + const obj = HTTPServer.__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_TestServer_wrap"] = function(pointer) { + const obj = TestServer.__construct(pointer); + return swift.memory.retain(obj); + }; + + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + /// Represents a Swift heap object like a class instance or an actor instance. + class SwiftHeapObject { + static __wrap(pointer, deinit, prototype) { + const obj = Object.create(prototype); + obj.pointer = pointer; + obj.hasReleased = false; + obj.deinit = deinit; + obj.registry = new FinalizationRegistry((pointer) => { + deinit(pointer); + }); + obj.registry.register(this, obj.pointer); + return obj; + } + + release() { + this.registry.unregister(this); + this.deinit(this.pointer); + } + } + class Converter extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Converter_deinit, Converter.prototype); + } + + + constructor() { + const ret = instance.exports.bjs_Converter_init(); + return Converter.__construct(ret); + } + toString(value) { + instance.exports.bjs_Converter_toString(this.pointer, value); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + } + class HTTPServer extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_HTTPServer_deinit, HTTPServer.prototype); + } + + + constructor() { + const ret = instance.exports.bjs_HTTPServer_init(); + return HTTPServer.__construct(ret); + } + call(method) { + instance.exports.bjs_HTTPServer_call(this.pointer, method); + } + } + class TestServer extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_TestServer_deinit, TestServer.prototype); + } + + + constructor() { + const ret = instance.exports.bjs_TestServer_init(); + return TestServer.__construct(ret); + } + call(method) { + instance.exports.bjs_TestServer_call(this.pointer, method); + } + } + const exports = { + Converter, + HTTPServer, + TestServer, + }; + + if (typeof globalThis.Networking === 'undefined') { + globalThis.Networking = {}; + } + if (typeof globalThis.Networking.API === 'undefined') { + globalThis.Networking.API = {}; + } + if (typeof globalThis.Networking.APIV2 === 'undefined') { + globalThis.Networking.APIV2 = {}; + } + if (typeof globalThis.Networking.APIV2.Internal === 'undefined') { + globalThis.Networking.APIV2.Internal = {}; + } + if (typeof globalThis.Utils === 'undefined') { + globalThis.Utils = {}; + } + globalThis.Utils.Converter = exports.Converter; + globalThis.Networking.API.HTTPServer = exports.HTTPServer; + globalThis.Networking.APIV2.Internal.TestServer = exports.TestServer; + + return exports; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.d.ts new file mode 100644 index 00000000..51b020ad --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.d.ts @@ -0,0 +1,131 @@ +// 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`. + +export const Theme: { + readonly Light: "light"; + readonly Dark: "dark"; + readonly Auto: "auto"; +}; +export type Theme = typeof Theme[keyof typeof Theme]; + +export enum TSTheme { + Light = "light", + Dark = "dark", + Auto = "auto", +} + +export const FeatureFlag: { + readonly Enabled: true; + readonly Disabled: false; +}; +export type FeatureFlag = typeof FeatureFlag[keyof typeof FeatureFlag]; + +export const HttpStatus: { + readonly Ok: 200; + readonly NotFound: 404; + readonly ServerError: 500; +}; +export type HttpStatus = typeof HttpStatus[keyof typeof HttpStatus]; + +export enum TSHttpStatus { + Ok = 200, + NotFound = 404, + ServerError = 500, +} + +export const Priority: { + readonly Lowest: 1; + readonly Low: 2; + readonly Medium: 3; + readonly High: 4; + readonly Highest: 5; +}; +export type Priority = typeof Priority[keyof typeof Priority]; + +export const FileSize: { + readonly Tiny: 1024; + readonly Small: 10240; + readonly Medium: 102400; + readonly Large: 1048576; +}; +export type FileSize = typeof FileSize[keyof typeof FileSize]; + +export const UserId: { + readonly Guest: 0; + readonly User: 1000; + readonly Admin: 9999; +}; +export type UserId = typeof UserId[keyof typeof UserId]; + +export const TokenId: { + readonly Invalid: 0; + readonly Session: 12345; + readonly Refresh: 67890; +}; +export type TokenId = typeof TokenId[keyof typeof TokenId]; + +export const SessionId: { + readonly None: 0; + readonly Active: 9876543210; + readonly Expired: 1234567890; +}; +export type SessionId = typeof SessionId[keyof typeof SessionId]; + +export const Precision: { + readonly Rough: 0.1; + readonly Normal: 0.01; + readonly Fine: 0.001; +}; +export type Precision = typeof Precision[keyof typeof Precision]; + +export const Ratio: { + readonly Quarter: 0.25; + readonly Half: 0.5; + readonly Golden: 1.618; + readonly Pi: 3.14159; +}; +export type Ratio = typeof Ratio[keyof typeof Ratio]; + +export type Exports = { + setTheme(theme: Theme): void; + getTheme(): Theme; + setTSTheme(theme: TSTheme): void; + getTSTheme(): TSTheme; + setFeatureFlag(flag: FeatureFlag): void; + getFeatureFlag(): FeatureFlag; + setHttpStatus(status: HttpStatus): void; + getHttpStatus(): HttpStatus; + setTSHttpStatus(status: TSHttpStatus): void; + getTSHttpStatus(): TSHttpStatus; + setPriority(priority: Priority): void; + getPriority(): Priority; + setFileSize(size: FileSize): void; + getFileSize(): FileSize; + setUserId(id: UserId): void; + getUserId(): UserId; + setTokenId(token: TokenId): void; + getTokenId(): TokenId; + setSessionId(session: SessionId): void; + getSessionId(): SessionId; + setPrecision(precision: Precision): void; + getPrecision(): Precision; + setRatio(ratio: Ratio): void; + getRatio(): Ratio; + setFeatureFlag(featureFlag: FeatureFlag): void; + getFeatureFlag(): FeatureFlag; + processTheme(theme: Theme): HttpStatus; + convertPriority(status: HttpStatus): Priority; + validateSession(session: SessionId): Theme; +} +export type Imports = { +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js new file mode 100644 index 00000000..2be034b4 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js @@ -0,0 +1,264 @@ +// 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`. + +export const Theme = { + Light: "light", + Dark: "dark", + Auto: "auto", +}; + +export const TSTheme = { + Light: "light", + Dark: "dark", + Auto: "auto", +}; + +export const FeatureFlag = { + Enabled: true, + Disabled: false, +}; + +export const HttpStatus = { + Ok: 200, + NotFound: 404, + ServerError: 500, +}; + +export const TSHttpStatus = { + Ok: 200, + NotFound: 404, + ServerError: 500, +}; + +export const Priority = { + Lowest: 1, + Low: 2, + Medium: 3, + High: 4, + Highest: 5, +}; + +export const FileSize = { + Tiny: 1024, + Small: 10240, + Medium: 102400, + Large: 1048576, +}; + +export const UserId = { + Guest: 0, + User: 1000, + Admin: 9999, +}; + +export const TokenId = { + Invalid: 0, + Session: 12345, + Refresh: 67890, +}; + +export const SessionId = { + None: 0, + Active: 9876543210, + Expired: 1234567890, +}; + +export const Precision = { + Rough: 0.1, + Normal: 0.01, + Fine: 0.001, +}; + +export const Ratio = { + Quarter: 0.25, + Half: 0.5, + Golden: 1.618, + Pi: 3.14159, +}; + + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + const bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + + + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + + return { + setTheme: function bjs_setTheme(theme) { + const themeBytes = textEncoder.encode(theme); + const themeId = swift.memory.retain(themeBytes); + instance.exports.bjs_setTheme(themeId, themeBytes.length); + swift.memory.release(themeId); + }, + getTheme: function bjs_getTheme() { + instance.exports.bjs_getTheme(); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + }, + setTSTheme: function bjs_setTSTheme(theme) { + const themeBytes = textEncoder.encode(theme); + const themeId = swift.memory.retain(themeBytes); + instance.exports.bjs_setTSTheme(themeId, themeBytes.length); + swift.memory.release(themeId); + }, + getTSTheme: function bjs_getTSTheme() { + instance.exports.bjs_getTSTheme(); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + }, + setFeatureFlag: function bjs_setFeatureFlag(flag) { + instance.exports.bjs_setFeatureFlag(flag); + }, + getFeatureFlag: function bjs_getFeatureFlag() { + const ret = instance.exports.bjs_getFeatureFlag(); + return ret !== 0; + }, + setHttpStatus: function bjs_setHttpStatus(status) { + instance.exports.bjs_setHttpStatus(status); + }, + getHttpStatus: function bjs_getHttpStatus() { + const ret = instance.exports.bjs_getHttpStatus(); + return ret; + }, + setTSHttpStatus: function bjs_setTSHttpStatus(status) { + instance.exports.bjs_setTSHttpStatus(status); + }, + getTSHttpStatus: function bjs_getTSHttpStatus() { + const ret = instance.exports.bjs_getTSHttpStatus(); + return ret; + }, + setPriority: function bjs_setPriority(priority) { + instance.exports.bjs_setPriority(priority); + }, + getPriority: function bjs_getPriority() { + const ret = instance.exports.bjs_getPriority(); + return ret; + }, + setFileSize: function bjs_setFileSize(size) { + instance.exports.bjs_setFileSize(size); + }, + getFileSize: function bjs_getFileSize() { + const ret = instance.exports.bjs_getFileSize(); + return ret; + }, + setUserId: function bjs_setUserId(id) { + instance.exports.bjs_setUserId(id); + }, + getUserId: function bjs_getUserId() { + const ret = instance.exports.bjs_getUserId(); + return ret; + }, + setTokenId: function bjs_setTokenId(token) { + instance.exports.bjs_setTokenId(token); + }, + getTokenId: function bjs_getTokenId() { + const ret = instance.exports.bjs_getTokenId(); + return ret; + }, + setSessionId: function bjs_setSessionId(session) { + instance.exports.bjs_setSessionId(session); + }, + getSessionId: function bjs_getSessionId() { + const ret = instance.exports.bjs_getSessionId(); + return ret; + }, + setPrecision: function bjs_setPrecision(precision) { + instance.exports.bjs_setPrecision(precision); + }, + getPrecision: function bjs_getPrecision() { + const ret = instance.exports.bjs_getPrecision(); + return ret; + }, + setRatio: function bjs_setRatio(ratio) { + instance.exports.bjs_setRatio(ratio); + }, + getRatio: function bjs_getRatio() { + const ret = instance.exports.bjs_getRatio(); + return ret; + }, + setFeatureFlag: function bjs_setFeatureFlag(featureFlag) { + instance.exports.bjs_setFeatureFlag(featureFlag); + }, + getFeatureFlag: function bjs_getFeatureFlag() { + const ret = instance.exports.bjs_getFeatureFlag(); + return ret !== 0; + }, + processTheme: function bjs_processTheme(theme) { + const themeBytes = textEncoder.encode(theme); + const themeId = swift.memory.retain(themeBytes); + const ret = instance.exports.bjs_processTheme(themeId, themeBytes.length); + swift.memory.release(themeId); + return ret; + }, + convertPriority: function bjs_convertPriority(status) { + const ret = instance.exports.bjs_convertPriority(status); + return ret; + }, + validateSession: function bjs_validateSession(session) { + instance.exports.bjs_validateSession(session); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + }, + }; + }, + } +} \ No newline at end of file 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 ffcbcd14..ccd371b7 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.d.ts @@ -4,6 +4,10 @@ // To update this file, just rebuild your project or run // `swift package bridge-js`. +export interface Animatable { + animate(keyframes: any, options: any): any; + getAnimations(options: any): any; +} export type Exports = { } export type Imports = { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js index 4d88bcdb..f81c7e47 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -46,10 +49,11 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_returnAnimatable"] = function bjs_returnAnimatable() { try { - let ret = options.imports.returnAnimatable(); + let ret = imports.returnAnimatable(); return swift.memory.retain(ret); } catch (error) { setException(error); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts new file mode 100644 index 00000000..2b0474bb --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts @@ -0,0 +1,29 @@ +// 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`. + +export interface ArrayBufferLike { + slice(begin: number, end: number): ArrayBufferLike; + readonly byteLength: number; +} +export interface WeirdNaming { + as(): void; + normalProperty: string; + for: string; + Any: string; +} +export type Exports = { +} +export type Imports = { + createArrayBuffer(): ArrayBufferLike; + createWeirdObject(): WeirdNaming; +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js new file mode 100644 index 00000000..6b5211e0 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js @@ -0,0 +1,168 @@ +// 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`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + const bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + 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"] = importObject["TestModule"] || {}; + TestModule["bjs_createArrayBuffer"] = function bjs_createArrayBuffer() { + try { + let ret = imports.createArrayBuffer(); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_createWeirdObject"] = function bjs_createWeirdObject() { + try { + let ret = imports.createWeirdObject(); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_ArrayBufferLike_byteLength_get"] = function bjs_ArrayBufferLike_byteLength_get(self) { + try { + let ret = swift.memory.getObject(self).byteLength; + return ret; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_ArrayBufferLike_slice"] = function bjs_ArrayBufferLike_slice(self, begin, end) { + try { + let ret = swift.memory.getObject(self).slice(begin, end); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_WeirdNaming_normalProperty_get"] = function bjs_WeirdNaming_normalProperty_get(self) { + try { + let ret = swift.memory.getObject(self).normalProperty; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming_normalProperty_set"] = function bjs_WeirdNaming_normalProperty_set(self, newValue) { + try { + const newValueObject = swift.memory.getObject(newValue); + swift.memory.release(newValue); + swift.memory.getObject(self).normalProperty = newValueObject; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming_for_get"] = function bjs_WeirdNaming_for_get(self) { + try { + let ret = swift.memory.getObject(self).for; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming_for_set"] = function bjs_WeirdNaming_for_set(self, newValue) { + try { + const newValueObject = swift.memory.getObject(newValue); + swift.memory.release(newValue); + swift.memory.getObject(self).for = newValueObject; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming_Any_get"] = function bjs_WeirdNaming_Any_get(self) { + try { + let ret = swift.memory.getObject(self).Any; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming_Any_set"] = function bjs_WeirdNaming_Any_set(self, newValue) { + try { + const newValueObject = swift.memory.getObject(newValue); + swift.memory.release(newValue); + swift.memory.getObject(self).Any = newValueObject; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming_as"] = function bjs_WeirdNaming_as(self) { + try { + swift.memory.getObject(self).as(); + } catch (error) { + setException(error); + } + } + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + + return { + + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.d.ts new file mode 100644 index 00000000..83fe3c14 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.d.ts @@ -0,0 +1,36 @@ +// 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`. + +export interface DatabaseConnection { + connect(url: string): void; + execute(query: string): any; + readonly isConnected: boolean; + connectionTimeout: number; +} +export interface Logger { + log(message: string): void; + error(message: string, error: any): void; + readonly level: string; +} +export interface ConfigManager { + get(key: string): any; + set(key: string, value: any): void; + readonly configPath: string; +} +export type Exports = { +} +export type Imports = { + createDatabaseConnection(config: any): DatabaseConnection; + createLogger(level: string): Logger; + getConfigManager(): ConfigManager; +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js new file mode 100644 index 00000000..d9a13b5e --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js @@ -0,0 +1,202 @@ +// 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`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + const bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + 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"] = importObject["TestModule"] || {}; + TestModule["bjs_createDatabaseConnection"] = function bjs_createDatabaseConnection(config) { + try { + let ret = imports.createDatabaseConnection(swift.memory.getObject(config)); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_createLogger"] = function bjs_createLogger(level) { + try { + const levelObject = swift.memory.getObject(level); + swift.memory.release(level); + let ret = imports.createLogger(levelObject); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_getConfigManager"] = function bjs_getConfigManager() { + try { + let ret = imports.getConfigManager(); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_DatabaseConnection_isConnected_get"] = function bjs_DatabaseConnection_isConnected_get(self) { + try { + let ret = swift.memory.getObject(self).isConnected; + return ret ? 1 : 0; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_DatabaseConnection_connectionTimeout_get"] = function bjs_DatabaseConnection_connectionTimeout_get(self) { + try { + let ret = swift.memory.getObject(self).connectionTimeout; + return ret; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_DatabaseConnection_connectionTimeout_set"] = function bjs_DatabaseConnection_connectionTimeout_set(self, newValue) { + try { + swift.memory.getObject(self).connectionTimeout = newValue; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_DatabaseConnection_connect"] = function bjs_DatabaseConnection_connect(self, url) { + try { + const urlObject = swift.memory.getObject(url); + swift.memory.release(url); + swift.memory.getObject(self).connect(urlObject); + } catch (error) { + setException(error); + } + } + TestModule["bjs_DatabaseConnection_execute"] = function bjs_DatabaseConnection_execute(self, query) { + try { + const queryObject = swift.memory.getObject(query); + swift.memory.release(query); + let ret = swift.memory.getObject(self).execute(queryObject); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_Logger_level_get"] = function bjs_Logger_level_get(self) { + try { + let ret = swift.memory.getObject(self).level; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_Logger_log"] = function bjs_Logger_log(self, message) { + try { + const messageObject = swift.memory.getObject(message); + swift.memory.release(message); + swift.memory.getObject(self).log(messageObject); + } catch (error) { + setException(error); + } + } + TestModule["bjs_Logger_error"] = function bjs_Logger_error(self, message, error) { + try { + const messageObject = swift.memory.getObject(message); + swift.memory.release(message); + swift.memory.getObject(self).error(messageObject, swift.memory.getObject(error)); + } catch (error) { + setException(error); + } + } + TestModule["bjs_ConfigManager_configPath_get"] = function bjs_ConfigManager_configPath_get(self) { + try { + let ret = swift.memory.getObject(self).configPath; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_ConfigManager_get"] = function bjs_ConfigManager_get(self, key) { + try { + const keyObject = swift.memory.getObject(key); + swift.memory.release(key); + let ret = swift.memory.getObject(self).get(keyObject); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_ConfigManager_set"] = function bjs_ConfigManager_set(self, key, value) { + try { + const keyObject = swift.memory.getObject(key); + swift.memory.release(key); + swift.memory.getObject(self).set(keyObject, swift.memory.getObject(value)); + } catch (error) { + setException(error); + } + } + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + + return { + + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts index b2ccecc4..c6e40399 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts @@ -7,6 +7,11 @@ export {}; declare global { + namespace MyModule { + namespace Utils { + namespacedFunction(): string; + } + } namespace Utils { namespace Converters { class Converter { @@ -26,11 +31,6 @@ declare global { } } } - namespace MyModule { - namespace Utils { - function namespacedFunction(): string; - } - } } /// Represents a Swift heap object like a class instance or an actor instance. diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js index dce99393..6915a61a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -46,6 +49,22 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } + // Wrapper functions for module: TestModule + if (!importObject["TestModule"]) { + importObject["TestModule"] = {}; + } + importObject["TestModule"]["bjs_Greeter_wrap"] = function(pointer) { + const obj = Greeter.__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_Converter_wrap"] = function(pointer) { + const obj = Converter.__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_UUID_wrap"] = function(pointer) { + const obj = UUID.__construct(pointer); + return swift.memory.retain(obj); + }; }, setInstance: (i) => { @@ -60,14 +79,16 @@ export async function createInstantiator(options, swift) { const js = swift.memory.heap; /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - constructor(pointer, deinit) { - this.pointer = pointer; - this.hasReleased = false; - this.deinit = deinit; - this.registry = new FinalizationRegistry((pointer) => { + static __wrap(pointer, deinit, prototype) { + const obj = Object.create(prototype); + obj.pointer = pointer; + obj.hasReleased = false; + obj.deinit = deinit; + obj.registry = new FinalizationRegistry((pointer) => { deinit(pointer); }); - this.registry.register(this, this.pointer); + obj.registry.register(this, obj.pointer); + return obj; } release() { @@ -76,12 +97,17 @@ export async function createInstantiator(options, swift) { } } class Greeter extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Greeter_deinit, Greeter.prototype); + } + + constructor(name) { const nameBytes = textEncoder.encode(name); const nameId = swift.memory.retain(nameBytes); const ret = instance.exports.bjs_Greeter_init(nameId, nameBytes.length); swift.memory.release(nameId); - super(ret, instance.exports.bjs_Greeter_deinit); + return Greeter.__construct(ret); } greet() { instance.exports.bjs_Greeter_greet(this.pointer); @@ -91,9 +117,14 @@ export async function createInstantiator(options, swift) { } } class Converter extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Converter_deinit, Converter.prototype); + } + + constructor() { const ret = instance.exports.bjs_Converter_init(); - super(ret, instance.exports.bjs_Converter_deinit); + return Converter.__construct(ret); } toString(value) { instance.exports.bjs_Converter_toString(this.pointer, value); @@ -103,6 +134,10 @@ export async function createInstantiator(options, swift) { } } class UUID extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_UUID_deinit, UUID.prototype); + } + uuidString() { instance.exports.bjs_UUID_uuidString(this.pointer); const ret = tmpRetString; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js index 1f4d6cbc..4873fc33 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -47,6 +50,7 @@ export async function createInstantiator(options, swift) { swift.memory.release(id); } + }, setInstance: (i) => { instance = i; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js index c6413b6b..b0dbaa19 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -46,10 +49,11 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_check"] = function bjs_check(a, b) { try { - options.imports.check(a, b); + imports.check(a, b !== 0); } catch (error) { setException(error); } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js index 01dbcb74..594dc9d5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -47,6 +50,7 @@ export async function createInstantiator(options, swift) { swift.memory.release(id); } + }, setInstance: (i) => { instance = i; @@ -73,8 +77,8 @@ export async function createInstantiator(options, swift) { return ret; }, checkBool: function bjs_checkBool() { - const ret = instance.exports.bjs_checkBool() !== 0; - return ret; + const ret = instance.exports.bjs_checkBool(); + return ret !== 0; }, }; }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js index 2a3292bc..a61149cd 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -46,10 +49,11 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_checkNumber"] = function bjs_checkNumber() { try { - let ret = options.imports.checkNumber(); + let ret = imports.checkNumber(); return ret; } catch (error) { setException(error); @@ -58,8 +62,8 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_checkBoolean"] = function bjs_checkBoolean() { try { - let ret = options.imports.checkBoolean(); - return ret !== 0; + let ret = imports.checkBoolean(); + return ret ? 1 : 0; } catch (error) { setException(error); return 0 diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.d.ts new file mode 100644 index 00000000..8f65849a --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.d.ts @@ -0,0 +1,48 @@ +// 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`. + +/// Represents a Swift heap object like a class instance or an actor instance. +export interface SwiftHeapObject { + /// Release the heap object. + /// + /// Note: Calling this method will release the heap object and it will no longer be accessible. + release(): void; +} +export interface PropertyHolder extends SwiftHeapObject { + getAllValues(): string; + intValue: number; + floatValue: number; + doubleValue: number; + boolValue: boolean; + stringValue: string; + readonly readonlyInt: number; + readonly readonlyFloat: number; + readonly readonlyDouble: number; + readonly readonlyBool: boolean; + readonly readonlyString: string; + jsObject: any; + sibling: PropertyHolder; + lazyValue: string; + readonly computedReadonly: number; + computedReadWrite: string; + observedProperty: number; +} +export type Exports = { + PropertyHolder: { + new(intValue: number, floatValue: number, doubleValue: number, boolValue: boolean, stringValue: string, jsObject: any): PropertyHolder; + } + createPropertyHolder(intValue: number, floatValue: number, doubleValue: number, boolValue: boolean, stringValue: string, jsObject: any): PropertyHolder; + testPropertyHolder(holder: PropertyHolder): string; +} +export type Imports = { +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js new file mode 100644 index 00000000..ffc1eab6 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js @@ -0,0 +1,242 @@ +// 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`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + const bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + // Wrapper functions for module: TestModule + if (!importObject["TestModule"]) { + importObject["TestModule"] = {}; + } + importObject["TestModule"]["bjs_PropertyHolder_wrap"] = function(pointer) { + const obj = PropertyHolder.__construct(pointer); + return swift.memory.retain(obj); + }; + + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + /// Represents a Swift heap object like a class instance or an actor instance. + class SwiftHeapObject { + static __wrap(pointer, deinit, prototype) { + const obj = Object.create(prototype); + obj.pointer = pointer; + obj.hasReleased = false; + obj.deinit = deinit; + obj.registry = new FinalizationRegistry((pointer) => { + deinit(pointer); + }); + obj.registry.register(this, obj.pointer); + return obj; + } + + release() { + this.registry.unregister(this); + this.deinit(this.pointer); + } + } + class PropertyHolder extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PropertyHolder_deinit, PropertyHolder.prototype); + } + + + constructor(intValue, floatValue, doubleValue, boolValue, stringValue, jsObject) { + const stringValueBytes = textEncoder.encode(stringValue); + const stringValueId = swift.memory.retain(stringValueBytes); + const ret = instance.exports.bjs_PropertyHolder_init(intValue, floatValue, doubleValue, boolValue, stringValueId, stringValueBytes.length, swift.memory.retain(jsObject)); + swift.memory.release(stringValueId); + return PropertyHolder.__construct(ret); + } + getAllValues() { + instance.exports.bjs_PropertyHolder_getAllValues(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + get intValue() { + const ret = instance.exports.bjs_PropertyHolder_intValue_get(this.pointer); + return ret; + } + set intValue(value) { + instance.exports.bjs_PropertyHolder_intValue_set(this.pointer, value); + } + get floatValue() { + const ret = instance.exports.bjs_PropertyHolder_floatValue_get(this.pointer); + return ret; + } + set floatValue(value) { + instance.exports.bjs_PropertyHolder_floatValue_set(this.pointer, value); + } + get doubleValue() { + const ret = instance.exports.bjs_PropertyHolder_doubleValue_get(this.pointer); + return ret; + } + set doubleValue(value) { + instance.exports.bjs_PropertyHolder_doubleValue_set(this.pointer, value); + } + get boolValue() { + const ret = instance.exports.bjs_PropertyHolder_boolValue_get(this.pointer); + return ret !== 0; + } + set boolValue(value) { + instance.exports.bjs_PropertyHolder_boolValue_set(this.pointer, value); + } + get stringValue() { + instance.exports.bjs_PropertyHolder_stringValue_get(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + set stringValue(value) { + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + instance.exports.bjs_PropertyHolder_stringValue_set(this.pointer, valueId, valueBytes.length); + swift.memory.release(valueId); + } + get readonlyInt() { + const ret = instance.exports.bjs_PropertyHolder_readonlyInt_get(this.pointer); + return ret; + } + get readonlyFloat() { + const ret = instance.exports.bjs_PropertyHolder_readonlyFloat_get(this.pointer); + return ret; + } + get readonlyDouble() { + const ret = instance.exports.bjs_PropertyHolder_readonlyDouble_get(this.pointer); + return ret; + } + get readonlyBool() { + const ret = instance.exports.bjs_PropertyHolder_readonlyBool_get(this.pointer); + return ret !== 0; + } + get readonlyString() { + instance.exports.bjs_PropertyHolder_readonlyString_get(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + get jsObject() { + const ret = instance.exports.bjs_PropertyHolder_jsObject_get(this.pointer); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + return ret1; + } + set jsObject(value) { + instance.exports.bjs_PropertyHolder_jsObject_set(this.pointer, swift.memory.retain(value)); + } + get sibling() { + const ret = instance.exports.bjs_PropertyHolder_sibling_get(this.pointer); + return PropertyHolder.__construct(ret); + } + set sibling(value) { + instance.exports.bjs_PropertyHolder_sibling_set(this.pointer, value.pointer); + } + get lazyValue() { + instance.exports.bjs_PropertyHolder_lazyValue_get(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + set lazyValue(value) { + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + instance.exports.bjs_PropertyHolder_lazyValue_set(this.pointer, valueId, valueBytes.length); + swift.memory.release(valueId); + } + get computedReadonly() { + const ret = instance.exports.bjs_PropertyHolder_computedReadonly_get(this.pointer); + return ret; + } + get computedReadWrite() { + instance.exports.bjs_PropertyHolder_computedReadWrite_get(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + set computedReadWrite(value) { + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + instance.exports.bjs_PropertyHolder_computedReadWrite_set(this.pointer, valueId, valueBytes.length); + swift.memory.release(valueId); + } + get observedProperty() { + const ret = instance.exports.bjs_PropertyHolder_observedProperty_get(this.pointer); + return ret; + } + set observedProperty(value) { + instance.exports.bjs_PropertyHolder_observedProperty_set(this.pointer, value); + } + } + return { + PropertyHolder, + createPropertyHolder: function bjs_createPropertyHolder(intValue, floatValue, doubleValue, boolValue, stringValue, jsObject) { + const stringValueBytes = textEncoder.encode(stringValue); + const stringValueId = swift.memory.retain(stringValueBytes); + const ret = instance.exports.bjs_createPropertyHolder(intValue, floatValue, doubleValue, boolValue, stringValueId, stringValueBytes.length, swift.memory.retain(jsObject)); + swift.memory.release(stringValueId); + return PropertyHolder.__construct(ret); + }, + testPropertyHolder: function bjs_testPropertyHolder(holder) { + instance.exports.bjs_testPropertyHolder(holder.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + }, + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js index 7c2e883d..ea47fb55 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -47,6 +50,7 @@ export async function createInstantiator(options, swift) { swift.memory.release(id); } + }, setInstance: (i) => { instance = i; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js index c12e6c1e..16ed1081 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -46,12 +49,13 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_checkString"] = function bjs_checkString(a) { try { const aObject = swift.memory.getObject(a); swift.memory.release(a); - options.imports.checkString(aObject); + imports.checkString(aObject); } catch (error) { setException(error); } @@ -60,7 +64,7 @@ export async function createInstantiator(options, swift) { try { const aObject = swift.memory.getObject(a); swift.memory.release(a); - options.imports.checkStringWithLength(aObject, b); + imports.checkStringWithLength(aObject, b); } catch (error) { setException(error); } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js index 0362f3c4..f98cea55 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -47,6 +50,7 @@ export async function createInstantiator(options, swift) { swift.memory.release(id); } + }, setInstance: (i) => { instance = i; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js index bb78c255..3220ae7b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -46,10 +49,11 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_checkString"] = function bjs_checkString() { try { - let ret = options.imports.checkString(); + let ret = imports.checkString(); tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts index fd376d57..8718463a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts @@ -14,11 +14,20 @@ export interface SwiftHeapObject { export interface Greeter extends SwiftHeapObject { greet(): string; changeName(name: string): void; + name: string; +} +export interface PublicGreeter extends SwiftHeapObject { +} +export interface PackageGreeter extends SwiftHeapObject { } export type Exports = { Greeter: { new(name: string): Greeter; } + PublicGreeter: { + } + PackageGreeter: { + } takeGreeter(greeter: Greeter): void; } export type Imports = { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js index 7a5938a1..fb995d24 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -46,6 +49,22 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } + // Wrapper functions for module: TestModule + if (!importObject["TestModule"]) { + importObject["TestModule"] = {}; + } + importObject["TestModule"]["bjs_Greeter_wrap"] = function(pointer) { + const obj = Greeter.__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_PublicGreeter_wrap"] = function(pointer) { + const obj = PublicGreeter.__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_PackageGreeter_wrap"] = function(pointer) { + const obj = PackageGreeter.__construct(pointer); + return swift.memory.retain(obj); + }; }, setInstance: (i) => { @@ -60,14 +79,16 @@ export async function createInstantiator(options, swift) { const js = swift.memory.heap; /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - constructor(pointer, deinit) { - this.pointer = pointer; - this.hasReleased = false; - this.deinit = deinit; - this.registry = new FinalizationRegistry((pointer) => { + static __wrap(pointer, deinit, prototype) { + const obj = Object.create(prototype); + obj.pointer = pointer; + obj.hasReleased = false; + obj.deinit = deinit; + obj.registry = new FinalizationRegistry((pointer) => { deinit(pointer); }); - this.registry.register(this, this.pointer); + obj.registry.register(this, obj.pointer); + return obj; } release() { @@ -76,12 +97,17 @@ export async function createInstantiator(options, swift) { } } class Greeter extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Greeter_deinit, Greeter.prototype); + } + + constructor(name) { const nameBytes = textEncoder.encode(name); const nameId = swift.memory.retain(nameBytes); const ret = instance.exports.bjs_Greeter_init(nameId, nameBytes.length); swift.memory.release(nameId); - super(ret, instance.exports.bjs_Greeter_deinit); + return Greeter.__construct(ret); } greet() { instance.exports.bjs_Greeter_greet(this.pointer); @@ -95,9 +121,35 @@ export async function createInstantiator(options, swift) { instance.exports.bjs_Greeter_changeName(this.pointer, nameId, nameBytes.length); swift.memory.release(nameId); } + get name() { + instance.exports.bjs_Greeter_name_get(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + set name(value) { + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + instance.exports.bjs_Greeter_name_set(this.pointer, valueId, valueBytes.length); + swift.memory.release(valueId); + } + } + class PublicGreeter extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PublicGreeter_deinit, PublicGreeter.prototype); + } + + } + class PackageGreeter extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PackageGreeter_deinit, PackageGreeter.prototype); + } + } return { Greeter, + PublicGreeter, + PackageGreeter, takeGreeter: function bjs_takeGreeter(greeter) { instance.exports.bjs_takeGreeter(greeter.pointer); }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.d.ts new file mode 100644 index 00000000..26d56fb6 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.d.ts @@ -0,0 +1,28 @@ +// 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`. + +export interface TypeScriptProcessor { + convert(ts: string): string; + validate(ts: string): boolean; + readonly version: string; +} +export interface CodeGenerator { + generate(input: any): string; + readonly outputFormat: string; +} +export type Exports = { +} +export type Imports = { + createTS2Skeleton(): TypeScriptProcessor; + createCodeGenerator(format: string): CodeGenerator; +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js new file mode 100644 index 00000000..c7671805 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js @@ -0,0 +1,140 @@ +// 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`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + const bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + 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"] = importObject["TestModule"] || {}; + TestModule["bjs_createTS2Skeleton"] = function bjs_createTS2Skeleton() { + try { + let ret = imports.createTS2Skeleton(); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_createCodeGenerator"] = function bjs_createCodeGenerator(format) { + try { + const formatObject = swift.memory.getObject(format); + swift.memory.release(format); + let ret = imports.createCodeGenerator(formatObject); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_TypeScriptProcessor_version_get"] = function bjs_TypeScriptProcessor_version_get(self) { + try { + let ret = swift.memory.getObject(self).version; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_TypeScriptProcessor_convert"] = function bjs_TypeScriptProcessor_convert(self, ts) { + try { + const tsObject = swift.memory.getObject(ts); + swift.memory.release(ts); + let ret = swift.memory.getObject(self).convert(tsObject); + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_TypeScriptProcessor_validate"] = function bjs_TypeScriptProcessor_validate(self, ts) { + try { + const tsObject = swift.memory.getObject(ts); + swift.memory.release(ts); + let ret = swift.memory.getObject(self).validate(tsObject); + return ret ? 1 : 0; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_CodeGenerator_outputFormat_get"] = function bjs_CodeGenerator_outputFormat_get(self) { + try { + let ret = swift.memory.getObject(self).outputFormat; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_CodeGenerator_generate"] = function bjs_CodeGenerator_generate(self, input) { + try { + let ret = swift.memory.getObject(self).generate(swift.memory.getObject(input)); + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + + return { + + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js index d0f9b623..b2089962 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -47,6 +50,7 @@ export async function createInstantiator(options, swift) { swift.memory.release(id); } + }, setInstance: (i) => { instance = i; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js index 30639b4a..2eb9dee5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -46,10 +49,11 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_checkSimple"] = function bjs_checkSimple(a) { try { - options.imports.checkSimple(a); + imports.checkSimple(a); } catch (error) { setException(error); } 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 bcbcf06f..24d3d8fa 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.d.ts @@ -4,6 +4,12 @@ // To update this file, just rebuild your project or run // `swift package bridge-js`. +export interface Greeter { + greet(): string; + changeName(name: string): void; + name: string; + readonly age: number; +} export type Exports = { } export type Imports = { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js index f5cdc9ef..48d15c7e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -46,13 +49,13 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_Greeter_init"] = function bjs_Greeter_init(name) { try { const nameObject = swift.memory.getObject(name); swift.memory.release(name); - let ret = new options.imports.Greeter(nameObject); - return swift.memory.retain(ret); + return swift.memory.retain(new imports.Greeter(nameObject)); } catch (error) { setException(error); return 0 diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js index c7086eda..c200c077 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -47,6 +50,7 @@ export async function createInstantiator(options, swift) { swift.memory.release(id); } + }, setInstance: (i) => { instance = i; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js index 2482082c..ca497688 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -46,10 +49,11 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_check"] = function bjs_check() { try { - options.imports.check(); + imports.check(); } catch (error) { setException(error); } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json new file mode 100644 index 00000000..c0d5347d --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json @@ -0,0 +1,171 @@ +{ + "classes" : [ + + ], + "enums" : [ + + ], + "functions" : [ + { + "abiName" : "bjs_asyncReturnVoid", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncReturnVoid", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripInt", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripInt", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "int" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripString", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripString", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripBool", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripBool", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "bool" : { + + } + } + } + ], + "returnType" : { + "bool" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripFloat", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripFloat", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "float" : { + + } + } + } + ], + "returnType" : { + "float" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripDouble", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripDouble", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripJSObject", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripJSObject", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "jsObject" : { + + } + } + } + ], + "returnType" : { + "jsObject" : { + + } + } + } + ], + "moduleName" : "TestModule" +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift new file mode 100644 index 00000000..33ddda25 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift @@ -0,0 +1,98 @@ +// 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(BridgeJS) import JavaScriptKit + +@_expose(wasm, "bjs_asyncReturnVoid") +@_cdecl("bjs_asyncReturnVoid") +public func _bjs_asyncReturnVoid() -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + await asyncReturnVoid() + } .jsObject + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripInt") +@_cdecl("bjs_asyncRoundTripInt") +public func _bjs_asyncRoundTripInt(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripInt(_: Int.bridgeJSLiftParameter(v)).jsValue + } .jsObject + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripString") +@_cdecl("bjs_asyncRoundTripString") +public func _bjs_asyncRoundTripString(vBytes: Int32, vLength: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripString(_: String.bridgeJSLiftParameter(vBytes, vLength)).jsValue + } .jsObject + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripBool") +@_cdecl("bjs_asyncRoundTripBool") +public func _bjs_asyncRoundTripBool(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripBool(_: Bool.bridgeJSLiftParameter(v)).jsValue + } .jsObject + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripFloat") +@_cdecl("bjs_asyncRoundTripFloat") +public func _bjs_asyncRoundTripFloat(v: Float32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripFloat(_: Float.bridgeJSLiftParameter(v)).jsValue + } .jsObject + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripDouble") +@_cdecl("bjs_asyncRoundTripDouble") +public func _bjs_asyncRoundTripDouble(v: Float64) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripDouble(_: Double.bridgeJSLiftParameter(v)).jsValue + } .jsObject + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripJSObject") +@_cdecl("bjs_asyncRoundTripJSObject") +public func _bjs_asyncRoundTripJSObject(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripJSObject(_: JSObject.bridgeJSLiftParameter(v)).jsValue + } .jsObject + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json new file mode 100644 index 00000000..b90bd40b --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json @@ -0,0 +1,215 @@ +{ + "classes" : [ + + ], + "enums" : [ + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "north" + }, + { + "associatedValues" : [ + + ], + "name" : "south" + }, + { + "associatedValues" : [ + + ], + "name" : "east" + }, + { + "associatedValues" : [ + + ], + "name" : "west" + } + ], + "emitStyle" : "const", + "name" : "Direction", + "swiftCallName" : "Direction" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "loading" + }, + { + "associatedValues" : [ + + ], + "name" : "success" + }, + { + "associatedValues" : [ + + ], + "name" : "error" + } + ], + "emitStyle" : "const", + "name" : "Status", + "swiftCallName" : "Status" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "north" + }, + { + "associatedValues" : [ + + ], + "name" : "south" + }, + { + "associatedValues" : [ + + ], + "name" : "east" + }, + { + "associatedValues" : [ + + ], + "name" : "west" + } + ], + "emitStyle" : "tsEnum", + "name" : "TSDirection", + "swiftCallName" : "TSDirection" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "success" + } + ], + "emitStyle" : "const", + "explicitAccessControl" : "public", + "name" : "PublicStatus", + "swiftCallName" : "PublicStatus" + } + ], + "functions" : [ + { + "abiName" : "bjs_setDirection", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setDirection", + "parameters" : [ + { + "label" : "_", + "name" : "direction", + "type" : { + "caseEnum" : { + "_0" : "Direction" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getDirection", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getDirection", + "parameters" : [ + + ], + "returnType" : { + "caseEnum" : { + "_0" : "Direction" + } + } + }, + { + "abiName" : "bjs_processDirection", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "processDirection", + "parameters" : [ + { + "label" : "_", + "name" : "input", + "type" : { + "caseEnum" : { + "_0" : "Direction" + } + } + } + ], + "returnType" : { + "caseEnum" : { + "_0" : "Status" + } + } + }, + { + "abiName" : "bjs_setTSDirection", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setTSDirection", + "parameters" : [ + { + "label" : "_", + "name" : "direction", + "type" : { + "caseEnum" : { + "_0" : "TSDirection" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getTSDirection", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getTSDirection", + "parameters" : [ + + ], + "returnType" : { + "caseEnum" : { + "_0" : "TSDirection" + } + } + } + ], + "moduleName" : "TestModule" +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift new file mode 100644 index 00000000..e222a2bb --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift @@ -0,0 +1,216 @@ +// 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(BridgeJS) import JavaScriptKit + +extension Direction { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Direction { + return Direction(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Direction { + return Direction(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .north + case 1: + self = .south + case 2: + self = .east + case 3: + self = .west + default: + return nil + } + } + + private var bridgeJSRawValue: Int32 { + switch self { + case .north: + return 0 + case .south: + return 1 + case .east: + return 2 + case .west: + return 3 + } + } +} + +extension Status { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Status { + return Status(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Status { + return Status(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .loading + case 1: + self = .success + case 2: + self = .error + default: + return nil + } + } + + private var bridgeJSRawValue: Int32 { + switch self { + case .loading: + return 0 + case .success: + return 1 + case .error: + return 2 + } + } +} + +extension TSDirection { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> TSDirection { + return TSDirection(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> TSDirection { + return TSDirection(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .north + case 1: + self = .south + case 2: + self = .east + case 3: + self = .west + default: + return nil + } + } + + private var bridgeJSRawValue: Int32 { + switch self { + case .north: + return 0 + case .south: + return 1 + case .east: + return 2 + case .west: + return 3 + } + } +} + +extension PublicStatus { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> PublicStatus { + return PublicStatus(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> PublicStatus { + return PublicStatus(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .success + default: + return nil + } + } + + private var bridgeJSRawValue: Int32 { + switch self { + case .success: + return 0 + } + } +} + +@_expose(wasm, "bjs_setDirection") +@_cdecl("bjs_setDirection") +public func _bjs_setDirection(direction: Int32) -> Void { + #if arch(wasm32) + setDirection(_: Direction.bridgeJSLiftParameter(direction)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getDirection") +@_cdecl("bjs_getDirection") +public func _bjs_getDirection() -> Int32 { + #if arch(wasm32) + let ret = getDirection() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_processDirection") +@_cdecl("bjs_processDirection") +public func _bjs_processDirection(input: Int32) -> Int32 { + #if arch(wasm32) + let ret = processDirection(_: Direction.bridgeJSLiftParameter(input)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setTSDirection") +@_cdecl("bjs_setTSDirection") +public func _bjs_setTSDirection(direction: Int32) -> Void { + #if arch(wasm32) + setTSDirection(_: TSDirection.bridgeJSLiftParameter(direction)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getTSDirection") +@_cdecl("bjs_getTSDirection") +public func _bjs_getTSDirection() -> Int32 { + #if arch(wasm32) + let ret = getTSDirection() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json new file mode 100644 index 00000000..2ec852f1 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json @@ -0,0 +1,289 @@ +{ + "classes" : [ + { + "constructor" : { + "abiName" : "bjs_Converter_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_Converter_toString", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "toString", + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + } + ], + "name" : "Converter", + "namespace" : [ + "Utils" + ], + "properties" : [ + + ], + "swiftCallName" : "Utils.Converter" + }, + { + "constructor" : { + "abiName" : "bjs_HTTPServer_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_HTTPServer_call", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "call", + "parameters" : [ + { + "label" : "_", + "name" : "method", + "type" : { + "caseEnum" : { + "_0" : "Networking.API.Method" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + } + ], + "name" : "HTTPServer", + "namespace" : [ + "Networking", + "API" + ], + "properties" : [ + + ], + "swiftCallName" : "Networking.API.HTTPServer" + }, + { + "constructor" : { + "abiName" : "bjs_TestServer_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_TestServer_call", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "call", + "parameters" : [ + { + "label" : "_", + "name" : "method", + "type" : { + "caseEnum" : { + "_0" : "Internal.SupportedMethod" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + } + ], + "name" : "TestServer", + "namespace" : [ + "Networking", + "APIV2", + "Internal" + ], + "properties" : [ + + ], + "swiftCallName" : "Internal.TestServer" + } + ], + "enums" : [ + { + "cases" : [ + + ], + "emitStyle" : "const", + "name" : "Utils", + "swiftCallName" : "Utils" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "get" + }, + { + "associatedValues" : [ + + ], + "name" : "post" + }, + { + "associatedValues" : [ + + ], + "name" : "put" + }, + { + "associatedValues" : [ + + ], + "name" : "delete" + } + ], + "emitStyle" : "const", + "name" : "Method", + "namespace" : [ + "Networking", + "API" + ], + "swiftCallName" : "Networking.API.Method" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "debug", + "rawValue" : "debug" + }, + { + "associatedValues" : [ + + ], + "name" : "info", + "rawValue" : "info" + }, + { + "associatedValues" : [ + + ], + "name" : "warning", + "rawValue" : "warning" + }, + { + "associatedValues" : [ + + ], + "name" : "error", + "rawValue" : "error" + } + ], + "emitStyle" : "const", + "name" : "LogLevel", + "namespace" : [ + "Configuration" + ], + "rawType" : "String", + "swiftCallName" : "Configuration.LogLevel" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "http", + "rawValue" : "80" + }, + { + "associatedValues" : [ + + ], + "name" : "https", + "rawValue" : "443" + }, + { + "associatedValues" : [ + + ], + "name" : "development", + "rawValue" : "3000" + } + ], + "emitStyle" : "const", + "name" : "Port", + "namespace" : [ + "Configuration" + ], + "rawType" : "Int", + "swiftCallName" : "Configuration.Port" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "get" + }, + { + "associatedValues" : [ + + ], + "name" : "post" + } + ], + "emitStyle" : "const", + "name" : "SupportedMethod", + "namespace" : [ + "Networking", + "APIV2", + "Internal" + ], + "swiftCallName" : "Internal.SupportedMethod" + } + ], + "functions" : [ + + ], + "moduleName" : "TestModule" +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift new file mode 100644 index 00000000..bb0fbe43 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift @@ -0,0 +1,218 @@ +// 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(BridgeJS) import JavaScriptKit + +extension Utils: _BridgedSwiftEnumNoPayload { +} + +extension Networking.API.Method { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Networking.API.Method { + return Networking.API.Method(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Networking.API.Method { + return Networking.API.Method(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .get + case 1: + self = .post + case 2: + self = .put + case 3: + self = .delete + default: + return nil + } + } + + private var bridgeJSRawValue: Int32 { + switch self { + case .get: + return 0 + case .post: + return 1 + case .put: + return 2 + case .delete: + return 3 + } + } +} + +extension Configuration.LogLevel: _BridgedSwiftEnumNoPayload { +} + +extension Configuration.Port: _BridgedSwiftEnumNoPayload { +} + +extension Internal.SupportedMethod { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Internal.SupportedMethod { + return Internal.SupportedMethod(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Internal.SupportedMethod { + return Internal.SupportedMethod(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .get + case 1: + self = .post + default: + return nil + } + } + + private var bridgeJSRawValue: Int32 { + switch self { + case .get: + return 0 + case .post: + return 1 + } + } +} + +@_expose(wasm, "bjs_Converter_init") +@_cdecl("bjs_Converter_init") +public func _bjs_Converter_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Utils.Converter() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Converter_toString") +@_cdecl("bjs_Converter_toString") +public func _bjs_Converter_toString(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + let ret = Utils.Converter.bridgeJSLiftParameter(_self).toString(value: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Converter_deinit") +@_cdecl("bjs_Converter_deinit") +public func _bjs_Converter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Utils.Converter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "TestModule", name: "bjs_Converter_wrap") + func _bjs_Converter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_Converter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_Converter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_HTTPServer_init") +@_cdecl("bjs_HTTPServer_init") +public func _bjs_HTTPServer_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Networking.API.HTTPServer() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_HTTPServer_call") +@_cdecl("bjs_HTTPServer_call") +public func _bjs_HTTPServer_call(_self: UnsafeMutableRawPointer, method: Int32) -> Void { + #if arch(wasm32) + Networking.API.HTTPServer.bridgeJSLiftParameter(_self).call(_: Networking.API.Method.bridgeJSLiftParameter(method)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_HTTPServer_deinit") +@_cdecl("bjs_HTTPServer_deinit") +public func _bjs_HTTPServer_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Networking.API.HTTPServer: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "TestModule", name: "bjs_HTTPServer_wrap") + func _bjs_HTTPServer_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_HTTPServer_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_HTTPServer_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_TestServer_init") +@_cdecl("bjs_TestServer_init") +public func _bjs_TestServer_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Internal.TestServer() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_TestServer_call") +@_cdecl("bjs_TestServer_call") +public func _bjs_TestServer_call(_self: UnsafeMutableRawPointer, method: Int32) -> Void { + #if arch(wasm32) + Internal.TestServer.bridgeJSLiftParameter(_self).call(_: Internal.SupportedMethod.bridgeJSLiftParameter(method)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_TestServer_deinit") +@_cdecl("bjs_TestServer_deinit") +public func _bjs_TestServer_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Internal.TestServer: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "TestModule", name: "bjs_TestServer_wrap") + func _bjs_TestServer_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_TestServer_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_TestServer_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json new file mode 100644 index 00000000..09ce5d6e --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json @@ -0,0 +1,1003 @@ +{ + "classes" : [ + + ], + "enums" : [ + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "light", + "rawValue" : "light" + }, + { + "associatedValues" : [ + + ], + "name" : "dark", + "rawValue" : "dark" + }, + { + "associatedValues" : [ + + ], + "name" : "auto", + "rawValue" : "auto" + } + ], + "emitStyle" : "const", + "name" : "Theme", + "rawType" : "String", + "swiftCallName" : "Theme" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "light", + "rawValue" : "light" + }, + { + "associatedValues" : [ + + ], + "name" : "dark", + "rawValue" : "dark" + }, + { + "associatedValues" : [ + + ], + "name" : "auto", + "rawValue" : "auto" + } + ], + "emitStyle" : "tsEnum", + "name" : "TSTheme", + "rawType" : "String", + "swiftCallName" : "TSTheme" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "enabled", + "rawValue" : "true" + }, + { + "associatedValues" : [ + + ], + "name" : "disabled", + "rawValue" : "false" + } + ], + "emitStyle" : "const", + "name" : "FeatureFlag", + "rawType" : "Bool", + "swiftCallName" : "FeatureFlag" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "ok", + "rawValue" : "200" + }, + { + "associatedValues" : [ + + ], + "name" : "notFound", + "rawValue" : "404" + }, + { + "associatedValues" : [ + + ], + "name" : "serverError", + "rawValue" : "500" + } + ], + "emitStyle" : "const", + "name" : "HttpStatus", + "rawType" : "Int", + "swiftCallName" : "HttpStatus" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "ok", + "rawValue" : "200" + }, + { + "associatedValues" : [ + + ], + "name" : "notFound", + "rawValue" : "404" + }, + { + "associatedValues" : [ + + ], + "name" : "serverError", + "rawValue" : "500" + } + ], + "emitStyle" : "tsEnum", + "name" : "TSHttpStatus", + "rawType" : "Int", + "swiftCallName" : "TSHttpStatus" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "lowest", + "rawValue" : "1" + }, + { + "associatedValues" : [ + + ], + "name" : "low", + "rawValue" : "2" + }, + { + "associatedValues" : [ + + ], + "name" : "medium", + "rawValue" : "3" + }, + { + "associatedValues" : [ + + ], + "name" : "high", + "rawValue" : "4" + }, + { + "associatedValues" : [ + + ], + "name" : "highest", + "rawValue" : "5" + } + ], + "emitStyle" : "const", + "name" : "Priority", + "rawType" : "Int32", + "swiftCallName" : "Priority" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "tiny", + "rawValue" : "1024" + }, + { + "associatedValues" : [ + + ], + "name" : "small", + "rawValue" : "10240" + }, + { + "associatedValues" : [ + + ], + "name" : "medium", + "rawValue" : "102400" + }, + { + "associatedValues" : [ + + ], + "name" : "large", + "rawValue" : "1048576" + } + ], + "emitStyle" : "const", + "name" : "FileSize", + "rawType" : "Int64", + "swiftCallName" : "FileSize" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "guest", + "rawValue" : "0" + }, + { + "associatedValues" : [ + + ], + "name" : "user", + "rawValue" : "1000" + }, + { + "associatedValues" : [ + + ], + "name" : "admin", + "rawValue" : "9999" + } + ], + "emitStyle" : "const", + "name" : "UserId", + "rawType" : "UInt", + "swiftCallName" : "UserId" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "invalid", + "rawValue" : "0" + }, + { + "associatedValues" : [ + + ], + "name" : "session", + "rawValue" : "12345" + }, + { + "associatedValues" : [ + + ], + "name" : "refresh", + "rawValue" : "67890" + } + ], + "emitStyle" : "const", + "name" : "TokenId", + "rawType" : "UInt32", + "swiftCallName" : "TokenId" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "none", + "rawValue" : "0" + }, + { + "associatedValues" : [ + + ], + "name" : "active", + "rawValue" : "9876543210" + }, + { + "associatedValues" : [ + + ], + "name" : "expired", + "rawValue" : "1234567890" + } + ], + "emitStyle" : "const", + "name" : "SessionId", + "rawType" : "UInt64", + "swiftCallName" : "SessionId" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "rough", + "rawValue" : "0.1" + }, + { + "associatedValues" : [ + + ], + "name" : "normal", + "rawValue" : "0.01" + }, + { + "associatedValues" : [ + + ], + "name" : "fine", + "rawValue" : "0.001" + } + ], + "emitStyle" : "const", + "name" : "Precision", + "rawType" : "Float", + "swiftCallName" : "Precision" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "quarter", + "rawValue" : "0.25" + }, + { + "associatedValues" : [ + + ], + "name" : "half", + "rawValue" : "0.5" + }, + { + "associatedValues" : [ + + ], + "name" : "golden", + "rawValue" : "1.618" + }, + { + "associatedValues" : [ + + ], + "name" : "pi", + "rawValue" : "3.14159" + } + ], + "emitStyle" : "const", + "name" : "Ratio", + "rawType" : "Double", + "swiftCallName" : "Ratio" + } + ], + "functions" : [ + { + "abiName" : "bjs_setTheme", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setTheme", + "parameters" : [ + { + "label" : "_", + "name" : "theme", + "type" : { + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getTheme", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getTheme", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" + } + } + }, + { + "abiName" : "bjs_setTSTheme", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setTSTheme", + "parameters" : [ + { + "label" : "_", + "name" : "theme", + "type" : { + "rawValueEnum" : { + "_0" : "TSTheme", + "_1" : "String" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getTSTheme", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getTSTheme", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "TSTheme", + "_1" : "String" + } + } + }, + { + "abiName" : "bjs_setFeatureFlag", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setFeatureFlag", + "parameters" : [ + { + "label" : "_", + "name" : "flag", + "type" : { + "rawValueEnum" : { + "_0" : "FeatureFlag", + "_1" : "Bool" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getFeatureFlag", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getFeatureFlag", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "FeatureFlag", + "_1" : "Bool" + } + } + }, + { + "abiName" : "bjs_setHttpStatus", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setHttpStatus", + "parameters" : [ + { + "label" : "_", + "name" : "status", + "type" : { + "rawValueEnum" : { + "_0" : "HttpStatus", + "_1" : "Int" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getHttpStatus", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getHttpStatus", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "HttpStatus", + "_1" : "Int" + } + } + }, + { + "abiName" : "bjs_setTSHttpStatus", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setTSHttpStatus", + "parameters" : [ + { + "label" : "_", + "name" : "status", + "type" : { + "rawValueEnum" : { + "_0" : "TSHttpStatus", + "_1" : "Int" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getTSHttpStatus", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getTSHttpStatus", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "TSHttpStatus", + "_1" : "Int" + } + } + }, + { + "abiName" : "bjs_setPriority", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setPriority", + "parameters" : [ + { + "label" : "_", + "name" : "priority", + "type" : { + "rawValueEnum" : { + "_0" : "Priority", + "_1" : "Int32" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getPriority", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getPriority", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Priority", + "_1" : "Int32" + } + } + }, + { + "abiName" : "bjs_setFileSize", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setFileSize", + "parameters" : [ + { + "label" : "_", + "name" : "size", + "type" : { + "rawValueEnum" : { + "_0" : "FileSize", + "_1" : "Int64" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getFileSize", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getFileSize", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "FileSize", + "_1" : "Int64" + } + } + }, + { + "abiName" : "bjs_setUserId", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setUserId", + "parameters" : [ + { + "label" : "_", + "name" : "id", + "type" : { + "rawValueEnum" : { + "_0" : "UserId", + "_1" : "UInt" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getUserId", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getUserId", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "UserId", + "_1" : "UInt" + } + } + }, + { + "abiName" : "bjs_setTokenId", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setTokenId", + "parameters" : [ + { + "label" : "_", + "name" : "token", + "type" : { + "rawValueEnum" : { + "_0" : "TokenId", + "_1" : "UInt32" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getTokenId", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getTokenId", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "TokenId", + "_1" : "UInt32" + } + } + }, + { + "abiName" : "bjs_setSessionId", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setSessionId", + "parameters" : [ + { + "label" : "_", + "name" : "session", + "type" : { + "rawValueEnum" : { + "_0" : "SessionId", + "_1" : "UInt64" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getSessionId", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getSessionId", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "SessionId", + "_1" : "UInt64" + } + } + }, + { + "abiName" : "bjs_setPrecision", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setPrecision", + "parameters" : [ + { + "label" : "_", + "name" : "precision", + "type" : { + "rawValueEnum" : { + "_0" : "Precision", + "_1" : "Float" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getPrecision", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getPrecision", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Precision", + "_1" : "Float" + } + } + }, + { + "abiName" : "bjs_setRatio", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setRatio", + "parameters" : [ + { + "label" : "_", + "name" : "ratio", + "type" : { + "rawValueEnum" : { + "_0" : "Ratio", + "_1" : "Double" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getRatio", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getRatio", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Ratio", + "_1" : "Double" + } + } + }, + { + "abiName" : "bjs_setFeatureFlag", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setFeatureFlag", + "parameters" : [ + { + "label" : "_", + "name" : "featureFlag", + "type" : { + "rawValueEnum" : { + "_0" : "FeatureFlag", + "_1" : "Bool" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getFeatureFlag", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getFeatureFlag", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "FeatureFlag", + "_1" : "Bool" + } + } + }, + { + "abiName" : "bjs_processTheme", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "processTheme", + "parameters" : [ + { + "label" : "_", + "name" : "theme", + "type" : { + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" + } + } + } + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "HttpStatus", + "_1" : "Int" + } + } + }, + { + "abiName" : "bjs_convertPriority", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "convertPriority", + "parameters" : [ + { + "label" : "_", + "name" : "status", + "type" : { + "rawValueEnum" : { + "_0" : "HttpStatus", + "_1" : "Int" + } + } + } + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Priority", + "_1" : "Int32" + } + } + }, + { + "abiName" : "bjs_validateSession", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "validateSession", + "parameters" : [ + { + "label" : "_", + "name" : "session", + "type" : { + "rawValueEnum" : { + "_0" : "SessionId", + "_1" : "UInt64" + } + } + } + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" + } + } + } + ], + "moduleName" : "TestModule" +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift new file mode 100644 index 00000000..e0a32e84 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift @@ -0,0 +1,349 @@ +// 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(BridgeJS) import JavaScriptKit + +extension Theme: _BridgedSwiftEnumNoPayload { +} + +extension TSTheme: _BridgedSwiftEnumNoPayload { +} + +extension FeatureFlag: _BridgedSwiftEnumNoPayload { +} + +extension HttpStatus: _BridgedSwiftEnumNoPayload { +} + +extension TSHttpStatus: _BridgedSwiftEnumNoPayload { +} + +extension Priority: _BridgedSwiftEnumNoPayload { +} + +extension FileSize: _BridgedSwiftEnumNoPayload { +} + +extension UserId: _BridgedSwiftEnumNoPayload { +} + +extension TokenId: _BridgedSwiftEnumNoPayload { +} + +extension SessionId: _BridgedSwiftEnumNoPayload { +} + +extension Precision: _BridgedSwiftEnumNoPayload { +} + +extension Ratio: _BridgedSwiftEnumNoPayload { +} + +@_expose(wasm, "bjs_setTheme") +@_cdecl("bjs_setTheme") +public func _bjs_setTheme(themeBytes: Int32, themeLength: Int32) -> Void { + #if arch(wasm32) + setTheme(_: Theme.bridgeJSLiftParameter(themeBytes, themeLength)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getTheme") +@_cdecl("bjs_getTheme") +public func _bjs_getTheme() -> Void { + #if arch(wasm32) + let ret = getTheme() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setTSTheme") +@_cdecl("bjs_setTSTheme") +public func _bjs_setTSTheme(themeBytes: Int32, themeLength: Int32) -> Void { + #if arch(wasm32) + setTSTheme(_: TSTheme.bridgeJSLiftParameter(themeBytes, themeLength)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getTSTheme") +@_cdecl("bjs_getTSTheme") +public func _bjs_getTSTheme() -> Void { + #if arch(wasm32) + let ret = getTSTheme() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setFeatureFlag") +@_cdecl("bjs_setFeatureFlag") +public func _bjs_setFeatureFlag(flag: Int32) -> Void { + #if arch(wasm32) + setFeatureFlag(_: FeatureFlag.bridgeJSLiftParameter(flag)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getFeatureFlag") +@_cdecl("bjs_getFeatureFlag") +public func _bjs_getFeatureFlag() -> Int32 { + #if arch(wasm32) + let ret = getFeatureFlag() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setHttpStatus") +@_cdecl("bjs_setHttpStatus") +public func _bjs_setHttpStatus(status: Int32) -> Void { + #if arch(wasm32) + setHttpStatus(_: HttpStatus.bridgeJSLiftParameter(status)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getHttpStatus") +@_cdecl("bjs_getHttpStatus") +public func _bjs_getHttpStatus() -> Int32 { + #if arch(wasm32) + let ret = getHttpStatus() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setTSHttpStatus") +@_cdecl("bjs_setTSHttpStatus") +public func _bjs_setTSHttpStatus(status: Int32) -> Void { + #if arch(wasm32) + setTSHttpStatus(_: TSHttpStatus.bridgeJSLiftParameter(status)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getTSHttpStatus") +@_cdecl("bjs_getTSHttpStatus") +public func _bjs_getTSHttpStatus() -> Int32 { + #if arch(wasm32) + let ret = getTSHttpStatus() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setPriority") +@_cdecl("bjs_setPriority") +public func _bjs_setPriority(priority: Int32) -> Void { + #if arch(wasm32) + setPriority(_: Priority.bridgeJSLiftParameter(priority)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getPriority") +@_cdecl("bjs_getPriority") +public func _bjs_getPriority() -> Int32 { + #if arch(wasm32) + let ret = getPriority() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setFileSize") +@_cdecl("bjs_setFileSize") +public func _bjs_setFileSize(size: Int32) -> Void { + #if arch(wasm32) + setFileSize(_: FileSize.bridgeJSLiftParameter(size)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getFileSize") +@_cdecl("bjs_getFileSize") +public func _bjs_getFileSize() -> Int32 { + #if arch(wasm32) + let ret = getFileSize() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setUserId") +@_cdecl("bjs_setUserId") +public func _bjs_setUserId(id: Int32) -> Void { + #if arch(wasm32) + setUserId(_: UserId.bridgeJSLiftParameter(id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getUserId") +@_cdecl("bjs_getUserId") +public func _bjs_getUserId() -> Int32 { + #if arch(wasm32) + let ret = getUserId() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setTokenId") +@_cdecl("bjs_setTokenId") +public func _bjs_setTokenId(token: Int32) -> Void { + #if arch(wasm32) + setTokenId(_: TokenId.bridgeJSLiftParameter(token)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getTokenId") +@_cdecl("bjs_getTokenId") +public func _bjs_getTokenId() -> Int32 { + #if arch(wasm32) + let ret = getTokenId() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setSessionId") +@_cdecl("bjs_setSessionId") +public func _bjs_setSessionId(session: Int32) -> Void { + #if arch(wasm32) + setSessionId(_: SessionId.bridgeJSLiftParameter(session)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getSessionId") +@_cdecl("bjs_getSessionId") +public func _bjs_getSessionId() -> Int32 { + #if arch(wasm32) + let ret = getSessionId() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setPrecision") +@_cdecl("bjs_setPrecision") +public func _bjs_setPrecision(precision: Float32) -> Void { + #if arch(wasm32) + setPrecision(_: Precision.bridgeJSLiftParameter(precision)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getPrecision") +@_cdecl("bjs_getPrecision") +public func _bjs_getPrecision() -> Float32 { + #if arch(wasm32) + let ret = getPrecision() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setRatio") +@_cdecl("bjs_setRatio") +public func _bjs_setRatio(ratio: Float64) -> Void { + #if arch(wasm32) + setRatio(_: Ratio.bridgeJSLiftParameter(ratio)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getRatio") +@_cdecl("bjs_getRatio") +public func _bjs_getRatio() -> Float64 { + #if arch(wasm32) + let ret = getRatio() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setFeatureFlag") +@_cdecl("bjs_setFeatureFlag") +public func _bjs_setFeatureFlag(featureFlag: Int32) -> Void { + #if arch(wasm32) + setFeatureFlag(_: FeatureFlag.bridgeJSLiftParameter(featureFlag)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getFeatureFlag") +@_cdecl("bjs_getFeatureFlag") +public func _bjs_getFeatureFlag() -> Int32 { + #if arch(wasm32) + let ret = getFeatureFlag() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_processTheme") +@_cdecl("bjs_processTheme") +public func _bjs_processTheme(themeBytes: Int32, themeLength: Int32) -> Int32 { + #if arch(wasm32) + let ret = processTheme(_: Theme.bridgeJSLiftParameter(themeBytes, themeLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_convertPriority") +@_cdecl("bjs_convertPriority") +public func _bjs_convertPriority(status: Int32) -> Int32 { + #if arch(wasm32) + let ret = convertPriority(_: HttpStatus.bridgeJSLiftParameter(status)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_validateSession") +@_cdecl("bjs_validateSession") +public func _bjs_validateSession(session: Int32) -> Void { + #if arch(wasm32) + let ret = validateSession(_: SessionId.bridgeJSLiftParameter(session)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json index 2a6440f1..bb81e29a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json @@ -41,7 +41,11 @@ "namespace" : [ "__Swift", "Foundation" - ] + ], + "properties" : [ + + ], + "swiftCallName" : "Greeter" }, { "constructor" : { @@ -84,7 +88,11 @@ "namespace" : [ "Utils", "Converters" - ] + ], + "properties" : [ + + ], + "swiftCallName" : "Converter" }, { "methods" : [ @@ -109,8 +117,15 @@ "namespace" : [ "__Swift", "Foundation" - ] + ], + "properties" : [ + + ], + "swiftCallName" : "UUID" } + ], + "enums" : [ + ], "functions" : [ { @@ -149,5 +164,6 @@ } } } - ] + ], + "moduleName" : "TestModule" } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift index fba15b29..f0e7f657 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift @@ -10,10 +10,8 @@ @_cdecl("bjs_plainFunction") public func _bjs_plainFunction() -> Void { #if arch(wasm32) - var ret = plainFunction() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = plainFunction() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -23,10 +21,8 @@ public func _bjs_plainFunction() -> Void { @_cdecl("bjs_namespacedFunction") public func _bjs_namespacedFunction() -> Void { #if arch(wasm32) - var ret = namespacedFunction() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = namespacedFunction() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -34,14 +30,10 @@ public func _bjs_namespacedFunction() -> Void { @_expose(wasm, "bjs_Greeter_init") @_cdecl("bjs_Greeter_init") -public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutableRawPointer { +public func _bjs_Greeter_init(nameBytes: Int32, nameLength: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) - let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in - _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) - return Int(nameLen) - } - let ret = Greeter(name: name) - return Unmanaged.passRetained(ret).toOpaque() + let ret = Greeter(name: String.bridgeJSLiftParameter(nameBytes, nameLength)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -51,10 +43,8 @@ public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutable @_cdecl("bjs_Greeter_greet") public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().greet() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Greeter.bridgeJSLiftParameter(_self).greet() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -66,12 +56,26 @@ public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } +extension Greeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "TestModule", name: "bjs_Greeter_wrap") + func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + @_expose(wasm, "bjs_Converter_init") @_cdecl("bjs_Converter_init") public func _bjs_Converter_init() -> UnsafeMutableRawPointer { #if arch(wasm32) let ret = Converter() - return Unmanaged.passRetained(ret).toOpaque() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -81,10 +85,8 @@ public func _bjs_Converter_init() -> UnsafeMutableRawPointer { @_cdecl("bjs_Converter_toString") public func _bjs_Converter_toString(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().toString(value: Int(value)) - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Converter.bridgeJSLiftParameter(_self).toString(value: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -96,14 +98,26 @@ public func _bjs_Converter_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } +extension Converter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "TestModule", name: "bjs_Converter_wrap") + func _bjs_Converter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_Converter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_Converter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + @_expose(wasm, "bjs_UUID_uuidString") @_cdecl("bjs_UUID_uuidString") public func _bjs_UUID_uuidString(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().uuidString() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = UUID.bridgeJSLiftParameter(_self).uuidString() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -113,4 +127,18 @@ public func _bjs_UUID_uuidString(_self: UnsafeMutableRawPointer) -> Void { @_cdecl("bjs_UUID_deinit") public func _bjs_UUID_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() +} + +extension UUID: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "TestModule", name: "bjs_UUID_wrap") + func _bjs_UUID_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_UUID_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_UUID_wrap(Unmanaged.passRetained(self).toOpaque())))) + } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json index 23fdeab8..c58f3c8e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json @@ -1,6 +1,9 @@ { "classes" : [ + ], + "enums" : [ + ], "functions" : [ { @@ -54,5 +57,6 @@ } } } - ] + ], + "moduleName" : "TestModule" } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.swift index c686c426..33097726 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.swift @@ -10,7 +10,7 @@ @_cdecl("bjs_check") public func _bjs_check(a: Int32, b: Float32, c: Float64, d: Int32) -> Void { #if arch(wasm32) - check(a: Int(a), b: b, c: c, d: d == 1) + check(a: Int.bridgeJSLiftParameter(a), b: Float.bridgeJSLiftParameter(b), c: Double.bridgeJSLiftParameter(c), d: Bool.bridgeJSLiftParameter(d)) #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json index f517c68a..ee29313b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json @@ -1,6 +1,9 @@ { "classes" : [ + ], + "enums" : [ + ], "functions" : [ { @@ -67,5 +70,6 @@ } } } - ] + ], + "moduleName" : "TestModule" } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.swift index 7356f2c8..63e5f03c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.swift @@ -11,7 +11,7 @@ public func _bjs_checkInt() -> Int32 { #if arch(wasm32) let ret = checkInt() - return Int32(ret) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -22,7 +22,7 @@ public func _bjs_checkInt() -> Int32 { public func _bjs_checkFloat() -> Float32 { #if arch(wasm32) let ret = checkFloat() - return Float32(ret) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -33,7 +33,7 @@ public func _bjs_checkFloat() -> Float32 { public func _bjs_checkDouble() -> Float64 { #if arch(wasm32) let ret = checkDouble() - return Float64(ret) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -44,7 +44,7 @@ public func _bjs_checkDouble() -> Float64 { public func _bjs_checkBool() -> Int32 { #if arch(wasm32) let ret = checkBool() - return Int32(ret ? 1 : 0) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.json new file mode 100644 index 00000000..e0c5e812 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.json @@ -0,0 +1,334 @@ +{ + "classes" : [ + { + "constructor" : { + "abiName" : "bjs_PropertyHolder_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "intValue", + "name" : "intValue", + "type" : { + "int" : { + + } + } + }, + { + "label" : "floatValue", + "name" : "floatValue", + "type" : { + "float" : { + + } + } + }, + { + "label" : "doubleValue", + "name" : "doubleValue", + "type" : { + "double" : { + + } + } + }, + { + "label" : "boolValue", + "name" : "boolValue", + "type" : { + "bool" : { + + } + } + }, + { + "label" : "stringValue", + "name" : "stringValue", + "type" : { + "string" : { + + } + } + }, + { + "label" : "jsObject", + "name" : "jsObject", + "type" : { + "jsObject" : { + + } + } + } + ] + }, + "methods" : [ + { + "abiName" : "bjs_PropertyHolder_getAllValues", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getAllValues", + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + } + } + ], + "name" : "PropertyHolder", + "properties" : [ + { + "isReadonly" : false, + "name" : "intValue", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "floatValue", + "type" : { + "float" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "doubleValue", + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "boolValue", + "type" : { + "bool" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "stringValue", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyInt", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyFloat", + "type" : { + "float" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyDouble", + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyBool", + "type" : { + "bool" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyString", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "jsObject", + "type" : { + "jsObject" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "sibling", + "type" : { + "swiftHeapObject" : { + "_0" : "PropertyHolder" + } + } + }, + { + "isReadonly" : false, + "name" : "lazyValue", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "computedReadonly", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "computedReadWrite", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "observedProperty", + "type" : { + "int" : { + + } + } + } + ], + "swiftCallName" : "PropertyHolder" + } + ], + "enums" : [ + + ], + "functions" : [ + { + "abiName" : "bjs_createPropertyHolder", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "createPropertyHolder", + "parameters" : [ + { + "label" : "intValue", + "name" : "intValue", + "type" : { + "int" : { + + } + } + }, + { + "label" : "floatValue", + "name" : "floatValue", + "type" : { + "float" : { + + } + } + }, + { + "label" : "doubleValue", + "name" : "doubleValue", + "type" : { + "double" : { + + } + } + }, + { + "label" : "boolValue", + "name" : "boolValue", + "type" : { + "bool" : { + + } + } + }, + { + "label" : "stringValue", + "name" : "stringValue", + "type" : { + "string" : { + + } + } + }, + { + "label" : "jsObject", + "name" : "jsObject", + "type" : { + "jsObject" : { + + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "PropertyHolder" + } + } + }, + { + "abiName" : "bjs_testPropertyHolder", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "testPropertyHolder", + "parameters" : [ + { + "label" : "holder", + "name" : "holder", + "type" : { + "swiftHeapObject" : { + "_0" : "PropertyHolder" + } + } + } + ], + "returnType" : { + "string" : { + + } + } + } + ], + "moduleName" : "TestModule" +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.swift new file mode 100644 index 00000000..822e5846 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.swift @@ -0,0 +1,347 @@ +// 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(BridgeJS) import JavaScriptKit + +@_expose(wasm, "bjs_createPropertyHolder") +@_cdecl("bjs_createPropertyHolder") +public func _bjs_createPropertyHolder(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLength: Int32, jsObject: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = createPropertyHolder(intValue: Int.bridgeJSLiftParameter(intValue), floatValue: Float.bridgeJSLiftParameter(floatValue), doubleValue: Double.bridgeJSLiftParameter(doubleValue), boolValue: Bool.bridgeJSLiftParameter(boolValue), stringValue: String.bridgeJSLiftParameter(stringValueBytes, stringValueLength), jsObject: JSObject.bridgeJSLiftParameter(jsObject)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_testPropertyHolder") +@_cdecl("bjs_testPropertyHolder") +public func _bjs_testPropertyHolder(holder: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = testPropertyHolder(holder: PropertyHolder.bridgeJSLiftParameter(holder)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_init") +@_cdecl("bjs_PropertyHolder_init") +public func _bjs_PropertyHolder_init(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLength: Int32, jsObject: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = PropertyHolder(intValue: Int.bridgeJSLiftParameter(intValue), floatValue: Float.bridgeJSLiftParameter(floatValue), doubleValue: Double.bridgeJSLiftParameter(doubleValue), boolValue: Bool.bridgeJSLiftParameter(boolValue), stringValue: String.bridgeJSLiftParameter(stringValueBytes, stringValueLength), jsObject: JSObject.bridgeJSLiftParameter(jsObject)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_getAllValues") +@_cdecl("bjs_PropertyHolder_getAllValues") +public func _bjs_PropertyHolder_getAllValues(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).getAllValues() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_intValue_get") +@_cdecl("bjs_PropertyHolder_intValue_get") +public func _bjs_PropertyHolder_intValue_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).intValue + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_intValue_set") +@_cdecl("bjs_PropertyHolder_intValue_set") +public func _bjs_PropertyHolder_intValue_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).intValue = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_floatValue_get") +@_cdecl("bjs_PropertyHolder_floatValue_get") +public func _bjs_PropertyHolder_floatValue_get(_self: UnsafeMutableRawPointer) -> Float32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).floatValue + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_floatValue_set") +@_cdecl("bjs_PropertyHolder_floatValue_set") +public func _bjs_PropertyHolder_floatValue_set(_self: UnsafeMutableRawPointer, value: Float32) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).floatValue = Float.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_doubleValue_get") +@_cdecl("bjs_PropertyHolder_doubleValue_get") +public func _bjs_PropertyHolder_doubleValue_get(_self: UnsafeMutableRawPointer) -> Float64 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).doubleValue + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_doubleValue_set") +@_cdecl("bjs_PropertyHolder_doubleValue_set") +public func _bjs_PropertyHolder_doubleValue_set(_self: UnsafeMutableRawPointer, value: Float64) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).doubleValue = Double.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_boolValue_get") +@_cdecl("bjs_PropertyHolder_boolValue_get") +public func _bjs_PropertyHolder_boolValue_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).boolValue + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_boolValue_set") +@_cdecl("bjs_PropertyHolder_boolValue_set") +public func _bjs_PropertyHolder_boolValue_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).boolValue = Bool.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_stringValue_get") +@_cdecl("bjs_PropertyHolder_stringValue_get") +public func _bjs_PropertyHolder_stringValue_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).stringValue + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_stringValue_set") +@_cdecl("bjs_PropertyHolder_stringValue_set") +public func _bjs_PropertyHolder_stringValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).stringValue = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyInt_get") +@_cdecl("bjs_PropertyHolder_readonlyInt_get") +public func _bjs_PropertyHolder_readonlyInt_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyInt + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyFloat_get") +@_cdecl("bjs_PropertyHolder_readonlyFloat_get") +public func _bjs_PropertyHolder_readonlyFloat_get(_self: UnsafeMutableRawPointer) -> Float32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyFloat + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyDouble_get") +@_cdecl("bjs_PropertyHolder_readonlyDouble_get") +public func _bjs_PropertyHolder_readonlyDouble_get(_self: UnsafeMutableRawPointer) -> Float64 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyDouble + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyBool_get") +@_cdecl("bjs_PropertyHolder_readonlyBool_get") +public func _bjs_PropertyHolder_readonlyBool_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyBool + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyString_get") +@_cdecl("bjs_PropertyHolder_readonlyString_get") +public func _bjs_PropertyHolder_readonlyString_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyString + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_jsObject_get") +@_cdecl("bjs_PropertyHolder_jsObject_get") +public func _bjs_PropertyHolder_jsObject_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).jsObject + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_jsObject_set") +@_cdecl("bjs_PropertyHolder_jsObject_set") +public func _bjs_PropertyHolder_jsObject_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).jsObject = JSObject.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_sibling_get") +@_cdecl("bjs_PropertyHolder_sibling_get") +public func _bjs_PropertyHolder_sibling_get(_self: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).sibling + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_sibling_set") +@_cdecl("bjs_PropertyHolder_sibling_set") +public func _bjs_PropertyHolder_sibling_set(_self: UnsafeMutableRawPointer, value: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).sibling = PropertyHolder.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_lazyValue_get") +@_cdecl("bjs_PropertyHolder_lazyValue_get") +public func _bjs_PropertyHolder_lazyValue_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).lazyValue + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_lazyValue_set") +@_cdecl("bjs_PropertyHolder_lazyValue_set") +public func _bjs_PropertyHolder_lazyValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).lazyValue = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_computedReadonly_get") +@_cdecl("bjs_PropertyHolder_computedReadonly_get") +public func _bjs_PropertyHolder_computedReadonly_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).computedReadonly + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_computedReadWrite_get") +@_cdecl("bjs_PropertyHolder_computedReadWrite_get") +public func _bjs_PropertyHolder_computedReadWrite_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).computedReadWrite + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_computedReadWrite_set") +@_cdecl("bjs_PropertyHolder_computedReadWrite_set") +public func _bjs_PropertyHolder_computedReadWrite_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).computedReadWrite = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_observedProperty_get") +@_cdecl("bjs_PropertyHolder_observedProperty_get") +public func _bjs_PropertyHolder_observedProperty_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).observedProperty + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_observedProperty_set") +@_cdecl("bjs_PropertyHolder_observedProperty_set") +public func _bjs_PropertyHolder_observedProperty_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).observedProperty = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_deinit") +@_cdecl("bjs_PropertyHolder_deinit") +public func _bjs_PropertyHolder_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension PropertyHolder: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "TestModule", name: "bjs_PropertyHolder_wrap") + func _bjs_PropertyHolder_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_PropertyHolder_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_PropertyHolder_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json index a86fb67e..22df1dc5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json @@ -1,6 +1,9 @@ { "classes" : [ + ], + "enums" : [ + ], "functions" : [ { @@ -27,5 +30,6 @@ } } } - ] + ], + "moduleName" : "TestModule" } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.swift index 69bd66b5..723a639c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.swift @@ -8,13 +8,9 @@ @_expose(wasm, "bjs_checkString") @_cdecl("bjs_checkString") -public func _bjs_checkString(aBytes: Int32, aLen: Int32) -> Void { +public func _bjs_checkString(aBytes: Int32, aLength: Int32) -> Void { #if arch(wasm32) - let a = String(unsafeUninitializedCapacity: Int(aLen)) { b in - _swift_js_init_memory(aBytes, b.baseAddress.unsafelyUnwrapped) - return Int(aLen) - } - checkString(a: a) + checkString(a: String.bridgeJSLiftParameter(aBytes, aLength)) #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json index b5536572..75439e36 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json @@ -1,6 +1,9 @@ { "classes" : [ + ], + "enums" : [ + ], "functions" : [ { @@ -19,5 +22,6 @@ } } } - ] + ], + "moduleName" : "TestModule" } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.swift index 536f0623..0475def5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.swift @@ -10,10 +10,8 @@ @_cdecl("bjs_checkString") public func _bjs_checkString() -> Void { #if arch(wasm32) - var ret = checkString() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = checkString() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json index d37a9254..ce506f51 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json @@ -61,8 +61,45 @@ } } ], - "name" : "Greeter" + "name" : "Greeter", + "properties" : [ + { + "isReadonly" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + } + ], + "swiftCallName" : "Greeter" + }, + { + "explicitAccessControl" : "public", + "methods" : [ + + ], + "name" : "PublicGreeter", + "properties" : [ + + ], + "swiftCallName" : "PublicGreeter" + }, + { + "explicitAccessControl" : "package", + "methods" : [ + + ], + "name" : "PackageGreeter", + "properties" : [ + + ], + "swiftCallName" : "PackageGreeter" } + ], + "enums" : [ + ], "functions" : [ { @@ -89,5 +126,6 @@ } } } - ] + ], + "moduleName" : "TestModule" } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift index d8ca05f2..81f5ccf4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift @@ -10,7 +10,7 @@ @_cdecl("bjs_takeGreeter") public func _bjs_takeGreeter(greeter: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - takeGreeter(greeter: Unmanaged.fromOpaque(greeter).takeUnretainedValue()) + takeGreeter(greeter: Greeter.bridgeJSLiftParameter(greeter)) #else fatalError("Only available on WebAssembly") #endif @@ -18,14 +18,10 @@ public func _bjs_takeGreeter(greeter: UnsafeMutableRawPointer) -> Void { @_expose(wasm, "bjs_Greeter_init") @_cdecl("bjs_Greeter_init") -public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutableRawPointer { +public func _bjs_Greeter_init(nameBytes: Int32, nameLength: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) - let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in - _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) - return Int(nameLen) - } - let ret = Greeter(name: name) - return Unmanaged.passRetained(ret).toOpaque() + let ret = Greeter(name: String.bridgeJSLiftParameter(nameBytes, nameLength)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -35,10 +31,8 @@ public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutable @_cdecl("bjs_Greeter_greet") public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().greet() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Greeter.bridgeJSLiftParameter(_self).greet() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -46,13 +40,30 @@ public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { @_expose(wasm, "bjs_Greeter_changeName") @_cdecl("bjs_Greeter_changeName") -public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: Int32, nameLen: Int32) -> Void { +public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: Int32, nameLength: Int32) -> Void { #if arch(wasm32) - let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in - _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) - return Int(nameLen) - } - Unmanaged.fromOpaque(_self).takeUnretainedValue().changeName(name: name) + Greeter.bridgeJSLiftParameter(_self).changeName(name: String.bridgeJSLiftParameter(nameBytes, nameLength)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_name_get") +@_cdecl("bjs_Greeter_name_get") +public func _bjs_Greeter_name_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = Greeter.bridgeJSLiftParameter(_self).name + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_name_set") +@_cdecl("bjs_Greeter_name_set") +public func _bjs_Greeter_name_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + Greeter.bridgeJSLiftParameter(_self).name = String.bridgeJSLiftParameter(valueBytes, valueLength) #else fatalError("Only available on WebAssembly") #endif @@ -62,4 +73,58 @@ public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: I @_cdecl("bjs_Greeter_deinit") public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() +} + +extension Greeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "TestModule", name: "bjs_Greeter_wrap") + func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_PublicGreeter_deinit") +@_cdecl("bjs_PublicGreeter_deinit") +public func _bjs_PublicGreeter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension PublicGreeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + public var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "TestModule", name: "bjs_PublicGreeter_wrap") + func _bjs_PublicGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_PublicGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_PublicGreeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_PackageGreeter_deinit") +@_cdecl("bjs_PackageGreeter_deinit") +public func _bjs_PackageGreeter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension PackageGreeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + package var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "TestModule", name: "bjs_PackageGreeter_wrap") + func _bjs_PackageGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_PackageGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_PackageGreeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json index 05363283..cc3184fb 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json @@ -1,6 +1,9 @@ { "classes" : [ + ], + "enums" : [ + ], "functions" : [ { @@ -19,5 +22,6 @@ } } } - ] + ], + "moduleName" : "TestModule" } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json index 96f875ab..413fe084 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json @@ -1,6 +1,9 @@ { "classes" : [ + ], + "enums" : [ + ], "functions" : [ { @@ -19,5 +22,6 @@ } } } - ] + ], + "moduleName" : "TestModule" } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift index 34841ae8..96fac13d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift @@ -15,7 +15,7 @@ func checkArray(_ a: JSObject) throws(JSException) -> Void { fatalError("Only available on WebAssembly") } #endif - bjs_checkArray(Int32(bitPattern: a.id)) + bjs_checkArray(a.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -30,7 +30,7 @@ func checkArrayWithLength(_ a: JSObject, _ b: Double) throws(JSException) -> Voi fatalError("Only available on WebAssembly") } #endif - bjs_checkArrayWithLength(Int32(bitPattern: a.id), b) + bjs_checkArrayWithLength(a.bridgeJSLowerParameter(), b.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -45,7 +45,7 @@ func checkArray(_ a: JSObject) throws(JSException) -> Void { fatalError("Only available on WebAssembly") } #endif - bjs_checkArray(Int32(bitPattern: a.id)) + bjs_checkArray(a.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift new file mode 100644 index 00000000..a8ecf8d5 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift @@ -0,0 +1,119 @@ +// 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(BridgeJS) import JavaScriptKit + +func asyncReturnVoid() throws(JSException) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncReturnVoid") + func bjs_asyncReturnVoid() -> Int32 + #else + func bjs_asyncReturnVoid() -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncReturnVoid() + if let error = _swift_js_take_exception() { + throw error + } + return JSPromise.bridgeJSLiftReturn(ret) +} + +func asyncRoundTripInt(_ v: Double) throws(JSException) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripInt") + func bjs_asyncRoundTripInt(_ v: Float64) -> Int32 + #else + func bjs_asyncRoundTripInt(_ v: Float64) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncRoundTripInt(v.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return JSPromise.bridgeJSLiftReturn(ret) +} + +func asyncRoundTripString(_ v: String) throws(JSException) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripString") + func bjs_asyncRoundTripString(_ v: Int32) -> Int32 + #else + func bjs_asyncRoundTripString(_ v: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncRoundTripString(v.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return JSPromise.bridgeJSLiftReturn(ret) +} + +func asyncRoundTripBool(_ v: Bool) throws(JSException) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripBool") + func bjs_asyncRoundTripBool(_ v: Int32) -> Int32 + #else + func bjs_asyncRoundTripBool(_ v: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncRoundTripBool(v.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return JSPromise.bridgeJSLiftReturn(ret) +} + +func asyncRoundTripFloat(_ v: Double) throws(JSException) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripFloat") + func bjs_asyncRoundTripFloat(_ v: Float64) -> Int32 + #else + func bjs_asyncRoundTripFloat(_ v: Float64) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncRoundTripFloat(v.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return JSPromise.bridgeJSLiftReturn(ret) +} + +func asyncRoundTripDouble(_ v: Double) throws(JSException) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripDouble") + func bjs_asyncRoundTripDouble(_ v: Float64) -> Int32 + #else + func bjs_asyncRoundTripDouble(_ v: Float64) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncRoundTripDouble(v.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return JSPromise.bridgeJSLiftReturn(ret) +} + +func asyncRoundTripJSObject(_ v: JSObject) throws(JSException) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripJSObject") + func bjs_asyncRoundTripJSObject(_ v: Int32) -> Int32 + #else + func bjs_asyncRoundTripJSObject(_ v: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncRoundTripJSObject(v.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return JSPromise.bridgeJSLiftReturn(ret) +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift index be9f524e..68f14808 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift @@ -19,18 +19,14 @@ func returnAnimatable() throws(JSException) -> Animatable { if let error = _swift_js_take_exception() { throw error } - return Animatable(takingThis: ret) + return Animatable.bridgeJSLiftReturn(ret) } -struct Animatable { - let this: JSObject +struct Animatable: _JSBridgedClass { + let jsObject: JSObject - init(this: JSObject) { - self.this = this - } - - init(takingThis this: Int32) { - self.this = JSObject(id: UInt32(bitPattern: this)) + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject } func animate(_ keyframes: JSObject, _ options: JSObject) throws(JSException) -> JSObject { @@ -42,11 +38,11 @@ struct Animatable { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_Animatable_animate(Int32(bitPattern: self.this.id), Int32(bitPattern: keyframes.id), Int32(bitPattern: options.id)) + let ret = bjs_Animatable_animate(self.bridgeJSLowerParameter(), keyframes.bridgeJSLowerParameter(), options.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return JSObject(id: UInt32(bitPattern: ret)) + return JSObject.bridgeJSLiftReturn(ret) } func getAnimations(_ options: JSObject) throws(JSException) -> JSObject { @@ -58,11 +54,11 @@ struct Animatable { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_Animatable_getAnimations(Int32(bitPattern: self.this.id), Int32(bitPattern: options.id)) + let ret = bjs_Animatable_getAnimations(self.bridgeJSLowerParameter(), options.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return JSObject(id: UInt32(bitPattern: ret)) + return JSObject.bridgeJSLiftReturn(ret) } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.swift new file mode 100644 index 00000000..7e35f921 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.swift @@ -0,0 +1,205 @@ +// 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(BridgeJS) import JavaScriptKit + +func createArrayBuffer() throws(JSException) -> ArrayBufferLike { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_createArrayBuffer") + func bjs_createArrayBuffer() -> Int32 + #else + func bjs_createArrayBuffer() -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_createArrayBuffer() + if let error = _swift_js_take_exception() { + throw error + } + return ArrayBufferLike.bridgeJSLiftReturn(ret) +} + +func createWeirdObject() throws(JSException) -> WeirdNaming { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_createWeirdObject") + func bjs_createWeirdObject() -> Int32 + #else + func bjs_createWeirdObject() -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_createWeirdObject() + if let error = _swift_js_take_exception() { + throw error + } + return WeirdNaming.bridgeJSLiftReturn(ret) +} + +struct ArrayBufferLike: _JSBridgedClass { + let jsObject: JSObject + + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject + } + + var byteLength: Double { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_ArrayBufferLike_byteLength_get") + func bjs_ArrayBufferLike_byteLength_get(_ self: Int32) -> Float64 + #else + func bjs_ArrayBufferLike_byteLength_get(_ self: Int32) -> Float64 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_ArrayBufferLike_byteLength_get(self.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return Double.bridgeJSLiftReturn(ret) + } + } + + func slice(_ begin: Double, _ end: Double) throws(JSException) -> ArrayBufferLike { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_ArrayBufferLike_slice") + func bjs_ArrayBufferLike_slice(_ self: Int32, _ begin: Float64, _ end: Float64) -> Int32 + #else + func bjs_ArrayBufferLike_slice(_ self: Int32, _ begin: Float64, _ end: Float64) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_ArrayBufferLike_slice(self.bridgeJSLowerParameter(), begin.bridgeJSLowerParameter(), end.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return ArrayBufferLike.bridgeJSLiftReturn(ret) + } + +} + +struct WeirdNaming: _JSBridgedClass { + let jsObject: JSObject + + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject + } + + var normalProperty: String { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_WeirdNaming_normalProperty_get") + func bjs_WeirdNaming_normalProperty_get(_ self: Int32) -> Int32 + #else + func bjs_WeirdNaming_normalProperty_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_WeirdNaming_normalProperty_get(self.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return String.bridgeJSLiftReturn(ret) + } + } + + func setNormalProperty(_ newValue: String) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_WeirdNaming_normalProperty_set") + func bjs_WeirdNaming_normalProperty_set(_ self: Int32, _ newValue: Int32) -> Void + #else + func bjs_WeirdNaming_normalProperty_set(_ self: Int32, _ newValue: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + bjs_WeirdNaming_normalProperty_set(self.bridgeJSLowerParameter(), newValue.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + } + + var `for`: String { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_WeirdNaming_for_get") + func bjs_WeirdNaming_for_get(_ self: Int32) -> Int32 + #else + func bjs_WeirdNaming_for_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_WeirdNaming_for_get(self.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return String.bridgeJSLiftReturn(ret) + } + } + + func setFor(_ newValue: String) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_WeirdNaming_for_set") + func bjs_WeirdNaming_for_set(_ self: Int32, _ newValue: Int32) -> Void + #else + func bjs_WeirdNaming_for_set(_ self: Int32, _ newValue: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + bjs_WeirdNaming_for_set(self.bridgeJSLowerParameter(), newValue.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + } + + var `Any`: String { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_WeirdNaming_Any_get") + func bjs_WeirdNaming_Any_get(_ self: Int32) -> Int32 + #else + func bjs_WeirdNaming_Any_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_WeirdNaming_Any_get(self.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return String.bridgeJSLiftReturn(ret) + } + } + + func setAny(_ newValue: String) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_WeirdNaming_Any_set") + func bjs_WeirdNaming_Any_set(_ self: Int32, _ newValue: Int32) -> Void + #else + func bjs_WeirdNaming_Any_set(_ self: Int32, _ newValue: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + bjs_WeirdNaming_Any_set(self.bridgeJSLowerParameter(), newValue.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + } + + func `as`() throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_WeirdNaming_as") + func bjs_WeirdNaming_as(_ self: Int32) -> Void + #else + func bjs_WeirdNaming_as(_ self: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + bjs_WeirdNaming_as(self.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + } + +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift new file mode 100644 index 00000000..810df368 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift @@ -0,0 +1,261 @@ +// 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(BridgeJS) import JavaScriptKit + +func createDatabaseConnection(_ config: JSObject) throws(JSException) -> DatabaseConnection { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_createDatabaseConnection") + func bjs_createDatabaseConnection(_ config: Int32) -> Int32 + #else + func bjs_createDatabaseConnection(_ config: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_createDatabaseConnection(config.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return DatabaseConnection.bridgeJSLiftReturn(ret) +} + +func createLogger(_ level: String) throws(JSException) -> Logger { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_createLogger") + func bjs_createLogger(_ level: Int32) -> Int32 + #else + func bjs_createLogger(_ level: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_createLogger(level.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return Logger.bridgeJSLiftReturn(ret) +} + +func getConfigManager() throws(JSException) -> ConfigManager { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_getConfigManager") + func bjs_getConfigManager() -> Int32 + #else + func bjs_getConfigManager() -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_getConfigManager() + if let error = _swift_js_take_exception() { + throw error + } + return ConfigManager.bridgeJSLiftReturn(ret) +} + +struct DatabaseConnection: _JSBridgedClass { + let jsObject: JSObject + + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject + } + + var isConnected: Bool { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_DatabaseConnection_isConnected_get") + func bjs_DatabaseConnection_isConnected_get(_ self: Int32) -> Int32 + #else + func bjs_DatabaseConnection_isConnected_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_DatabaseConnection_isConnected_get(self.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return Bool.bridgeJSLiftReturn(ret) + } + } + + var connectionTimeout: Double { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_DatabaseConnection_connectionTimeout_get") + func bjs_DatabaseConnection_connectionTimeout_get(_ self: Int32) -> Float64 + #else + func bjs_DatabaseConnection_connectionTimeout_get(_ self: Int32) -> Float64 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_DatabaseConnection_connectionTimeout_get(self.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return Double.bridgeJSLiftReturn(ret) + } + } + + func setConnectionTimeout(_ newValue: Double) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_DatabaseConnection_connectionTimeout_set") + func bjs_DatabaseConnection_connectionTimeout_set(_ self: Int32, _ newValue: Float64) -> Void + #else + func bjs_DatabaseConnection_connectionTimeout_set(_ self: Int32, _ newValue: Float64) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + bjs_DatabaseConnection_connectionTimeout_set(self.bridgeJSLowerParameter(), newValue.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + } + + func connect(_ url: String) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_DatabaseConnection_connect") + func bjs_DatabaseConnection_connect(_ self: Int32, _ url: Int32) -> Void + #else + func bjs_DatabaseConnection_connect(_ self: Int32, _ url: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + bjs_DatabaseConnection_connect(self.bridgeJSLowerParameter(), url.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + } + + func execute(_ query: String) throws(JSException) -> JSObject { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_DatabaseConnection_execute") + func bjs_DatabaseConnection_execute(_ self: Int32, _ query: Int32) -> Int32 + #else + func bjs_DatabaseConnection_execute(_ self: Int32, _ query: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_DatabaseConnection_execute(self.bridgeJSLowerParameter(), query.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return JSObject.bridgeJSLiftReturn(ret) + } + +} + +struct Logger: _JSBridgedClass { + let jsObject: JSObject + + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject + } + + var level: String { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_Logger_level_get") + func bjs_Logger_level_get(_ self: Int32) -> Int32 + #else + func bjs_Logger_level_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_Logger_level_get(self.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return String.bridgeJSLiftReturn(ret) + } + } + + func log(_ message: String) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_Logger_log") + func bjs_Logger_log(_ self: Int32, _ message: Int32) -> Void + #else + func bjs_Logger_log(_ self: Int32, _ message: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + bjs_Logger_log(self.bridgeJSLowerParameter(), message.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + } + + func error(_ message: String, _ error: JSObject) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_Logger_error") + func bjs_Logger_error(_ self: Int32, _ message: Int32, _ error: Int32) -> Void + #else + func bjs_Logger_error(_ self: Int32, _ message: Int32, _ error: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + bjs_Logger_error(self.bridgeJSLowerParameter(), message.bridgeJSLowerParameter(), error.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + } + +} + +struct ConfigManager: _JSBridgedClass { + let jsObject: JSObject + + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject + } + + var configPath: String { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_ConfigManager_configPath_get") + func bjs_ConfigManager_configPath_get(_ self: Int32) -> Int32 + #else + func bjs_ConfigManager_configPath_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_ConfigManager_configPath_get(self.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return String.bridgeJSLiftReturn(ret) + } + } + + func get(_ key: String) throws(JSException) -> JSObject { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_ConfigManager_get") + func bjs_ConfigManager_get(_ self: Int32, _ key: Int32) -> Int32 + #else + func bjs_ConfigManager_get(_ self: Int32, _ key: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_ConfigManager_get(self.bridgeJSLowerParameter(), key.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return JSObject.bridgeJSLiftReturn(ret) + } + + func set(_ key: String, _ value: JSObject) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_ConfigManager_set") + func bjs_ConfigManager_set(_ self: Int32, _ key: Int32, _ value: Int32) -> Void + #else + func bjs_ConfigManager_set(_ self: Int32, _ key: Int32, _ value: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + bjs_ConfigManager_set(self.bridgeJSLowerParameter(), key.bridgeJSLowerParameter(), value.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + } + +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift index c47f5f40..30f66a26 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift @@ -15,7 +15,7 @@ func check(_ a: Double, _ b: Bool) throws(JSException) -> Void { fatalError("Only available on WebAssembly") } #endif - bjs_check(a, Int32(b ? 1 : 0)) + bjs_check(a.bridgeJSLowerParameter(), b.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift index cf26a52f..29ba81c6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift @@ -19,7 +19,7 @@ func checkNumber() throws(JSException) -> Double { if let error = _swift_js_take_exception() { throw error } - return Double(ret) + return Double.bridgeJSLiftReturn(ret) } func checkBoolean() throws(JSException) -> Bool { @@ -35,5 +35,5 @@ func checkBoolean() throws(JSException) -> Bool { if let error = _swift_js_take_exception() { throw error } - return ret == 1 + return Bool.bridgeJSLiftReturn(ret) } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift index aabffacc..99215a30 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift @@ -15,11 +15,7 @@ func checkString(_ a: String) throws(JSException) -> Void { fatalError("Only available on WebAssembly") } #endif - var a = a - let aId = a.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_checkString(aId) + bjs_checkString(a.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -34,11 +30,7 @@ func checkStringWithLength(_ a: String, _ b: Double) throws(JSException) -> Void fatalError("Only available on WebAssembly") } #endif - var a = a - let aId = a.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_checkStringWithLength(aId, b) + bjs_checkStringWithLength(a.bridgeJSLowerParameter(), b.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringReturn.swift index af7b5162..05bb8aea 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringReturn.swift @@ -19,8 +19,5 @@ func checkString() throws(JSException) -> String { if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift new file mode 100644 index 00000000..0b17f13b --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift @@ -0,0 +1,141 @@ +// 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(BridgeJS) import JavaScriptKit + +func createTS2Skeleton() throws(JSException) -> TypeScriptProcessor { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_createTS2Skeleton") + func bjs_createTS2Skeleton() -> Int32 + #else + func bjs_createTS2Skeleton() -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_createTS2Skeleton() + if let error = _swift_js_take_exception() { + throw error + } + return TypeScriptProcessor.bridgeJSLiftReturn(ret) +} + +func createCodeGenerator(_ format: String) throws(JSException) -> CodeGenerator { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_createCodeGenerator") + func bjs_createCodeGenerator(_ format: Int32) -> Int32 + #else + func bjs_createCodeGenerator(_ format: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_createCodeGenerator(format.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return CodeGenerator.bridgeJSLiftReturn(ret) +} + +struct TypeScriptProcessor: _JSBridgedClass { + let jsObject: JSObject + + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject + } + + var version: String { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_TypeScriptProcessor_version_get") + func bjs_TypeScriptProcessor_version_get(_ self: Int32) -> Int32 + #else + func bjs_TypeScriptProcessor_version_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_TypeScriptProcessor_version_get(self.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return String.bridgeJSLiftReturn(ret) + } + } + + func convert(_ ts: String) throws(JSException) -> String { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_TypeScriptProcessor_convert") + func bjs_TypeScriptProcessor_convert(_ self: Int32, _ ts: Int32) -> Int32 + #else + func bjs_TypeScriptProcessor_convert(_ self: Int32, _ ts: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_TypeScriptProcessor_convert(self.bridgeJSLowerParameter(), ts.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return String.bridgeJSLiftReturn(ret) + } + + func validate(_ ts: String) throws(JSException) -> Bool { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_TypeScriptProcessor_validate") + func bjs_TypeScriptProcessor_validate(_ self: Int32, _ ts: Int32) -> Int32 + #else + func bjs_TypeScriptProcessor_validate(_ self: Int32, _ ts: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_TypeScriptProcessor_validate(self.bridgeJSLowerParameter(), ts.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return Bool.bridgeJSLiftReturn(ret) + } + +} + +struct CodeGenerator: _JSBridgedClass { + let jsObject: JSObject + + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject + } + + var outputFormat: String { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_CodeGenerator_outputFormat_get") + func bjs_CodeGenerator_outputFormat_get(_ self: Int32) -> Int32 + #else + func bjs_CodeGenerator_outputFormat_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_CodeGenerator_outputFormat_get(self.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return String.bridgeJSLiftReturn(ret) + } + } + + func generate(_ input: JSObject) throws(JSException) -> String { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_CodeGenerator_generate") + func bjs_CodeGenerator_generate(_ self: Int32, _ input: Int32) -> Int32 + #else + func bjs_CodeGenerator_generate(_ self: Int32, _ input: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_CodeGenerator_generate(self.bridgeJSLowerParameter(), input.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return String.bridgeJSLiftReturn(ret) + } + +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeAlias.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeAlias.swift index 7523101f..d8b18463 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeAlias.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeAlias.swift @@ -15,7 +15,7 @@ func checkSimple(_ a: Double) throws(JSException) -> Void { fatalError("Only available on WebAssembly") } #endif - bjs_checkSimple(a) + bjs_checkSimple(a.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift index 7a1f2a2c..455b38bc 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift @@ -6,15 +6,11 @@ @_spi(BridgeJS) import JavaScriptKit -struct Greeter { - let this: JSObject +struct Greeter: _JSBridgedClass { + let jsObject: JSObject - init(this: JSObject) { - self.this = this - } - - init(takingThis this: Int32) { - self.this = JSObject(id: UInt32(bitPattern: this)) + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject } init(_ name: String) throws(JSException) { @@ -26,15 +22,11 @@ struct Greeter { fatalError("Only available on WebAssembly") } #endif - var name = name - let nameId = name.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - let ret = bjs_Greeter_init(nameId) + let ret = bjs_Greeter_init(name.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - self.this = JSObject(id: UInt32(bitPattern: ret)) + self.jsObject = JSObject(id: UInt32(bitPattern: ret)) } var name: String { @@ -47,14 +39,11 @@ struct Greeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_Greeter_name_get(Int32(bitPattern: self.this.id)) + let ret = bjs_Greeter_name_get(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } } @@ -67,11 +56,7 @@ struct Greeter { fatalError("Only available on WebAssembly") } #endif - var newValue = newValue - let newValueId = newValue.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_Greeter_name_set(Int32(bitPattern: self.this.id), newValueId) + bjs_Greeter_name_set(self.bridgeJSLowerParameter(), newValue.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -87,11 +72,11 @@ struct Greeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_Greeter_age_get(Int32(bitPattern: self.this.id)) + let ret = bjs_Greeter_age_get(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return Double(ret) + return Double.bridgeJSLiftReturn(ret) } } @@ -104,14 +89,11 @@ struct Greeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_Greeter_greet(Int32(bitPattern: self.this.id)) + let ret = bjs_Greeter_greet(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } func changeName(_ name: String) throws(JSException) -> Void { @@ -123,11 +105,7 @@ struct Greeter { fatalError("Only available on WebAssembly") } #endif - var name = name - let nameId = name.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_Greeter_changeName(Int32(bitPattern: self.this.id), nameId) + bjs_Greeter_changeName(self.bridgeJSLowerParameter(), name.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/PackageToJS/Sources/BridgeJSUtilities b/Plugins/PackageToJS/Sources/BridgeJSUtilities new file mode 120000 index 00000000..52bf489d --- /dev/null +++ b/Plugins/PackageToJS/Sources/BridgeJSUtilities @@ -0,0 +1 @@ +../../../Plugins/BridgeJS/Sources/BridgeJSUtilities \ No newline at end of file diff --git a/Plugins/PackageToJS/Sources/PackageToJS.swift b/Plugins/PackageToJS/Sources/PackageToJS.swift index 9a3f4c54..c486c327 100644 --- a/Plugins/PackageToJS/Sources/PackageToJS.swift +++ b/Plugins/PackageToJS/Sources/PackageToJS.swift @@ -295,7 +295,7 @@ final class DefaultPackagingSystem: PackagingSystem { private let printWarning: (String) -> Void private let which: (String) throws -> URL - init(printWarning: @escaping (String) -> Void, which: @escaping (String) throws -> URL = which(_:)) { + init(printWarning: @escaping (String) -> Void, which: @escaping (String) throws -> URL) { self.printWarning = printWarning self.which = which } @@ -323,6 +323,7 @@ final class DefaultPackagingSystem: PackagingSystem { } internal func which(_ executable: String) throws -> URL { + let environment = ProcessInfo.processInfo.environment func checkCandidate(_ candidate: URL) -> Bool { var isDirectory: ObjCBool = false let fileExists = FileManager.default.fileExists(atPath: candidate.path, isDirectory: &isDirectory) @@ -330,9 +331,9 @@ internal func which(_ executable: String) throws -> URL { } do { // Check overriding environment variable - let envVariable = executable.uppercased().replacingOccurrences(of: "-", with: "_") + "_PATH" - if let path = ProcessInfo.processInfo.environment[envVariable] { - let url = URL(fileURLWithPath: path).appendingPathComponent(executable) + let envVariable = "JAVASCRIPTKIT_" + executable.uppercased().replacingOccurrences(of: "-", with: "_") + "_EXEC" + if let executablePath = environment[envVariable] { + let url = URL(fileURLWithPath: executablePath) if checkCandidate(url) { return url } @@ -344,7 +345,7 @@ internal func which(_ executable: String) throws -> URL { #else pathSeparator = ":" #endif - let paths = ProcessInfo.processInfo.environment["PATH"]!.split(separator: pathSeparator) + let paths = environment["PATH"]?.split(separator: pathSeparator) ?? [] for path in paths { let url = URL(fileURLWithPath: String(path)).appendingPathComponent(executable) if checkCandidate(url) { diff --git a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift index 1f15f267..dc9958b9 100644 --- a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift +++ b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift @@ -166,7 +166,7 @@ struct PackageToJSPlugin: CommandPlugin { } } - static let JAVASCRIPTKIT_PACKAGE_ID: Package.ID = "javascriptkit" + static let JAVASCRIPTKIT_PRODUCT_ID: Product.ID = "JavaScriptKit" func performBuildCommand(context: PluginContext, arguments: [String]) throws { if arguments.contains(where: { ["-h", "--help"].contains($0) }) { @@ -396,7 +396,7 @@ struct PackageToJSPlugin: CommandPlugin { guard let selfPackage = findPackageInDependencies( package: package, - id: Self.JAVASCRIPTKIT_PACKAGE_ID + including: Self.JAVASCRIPTKIT_PRODUCT_ID ) else { throw PackageToJSError("Failed to find JavaScriptKit in dependencies!?") @@ -649,12 +649,24 @@ extension PackageManager.BuildResult { } } -private func findPackageInDependencies(package: Package, id: Package.ID) -> Package? { +/// Find the package that contains the product with the given name +/// - Parameters: +/// - package: The package to search in +/// - productName: The name of the product to find +/// - Returns: The package that contains the product with the given name +/// - Note: Why not use `Package.ID`? `Package.ID` is not always equal to the package repository name +/// as it's derived from the directory name when the package is dependent on another package as a +// local package. +private func findPackageInDependencies(package: Package, including productName: String) -> Package? { var visited: Set = [] func visit(package: Package) -> Package? { if visited.contains(package.id) { return nil } visited.insert(package.id) - if package.id == id { return package } + for product in package.products { + if product.name == productName { + return package + } + } for dependency in package.dependencies { if let found = visit(package: dependency.package) { return found @@ -762,7 +774,7 @@ extension PackagingPlanner { ) { let outputBaseName = outputDir.lastPathComponent let (configuration, triple) = PackageToJS.deriveBuildConfiguration(wasmProductArtifact: wasmProductArtifact) - let system = DefaultPackagingSystem(printWarning: printStderr) + let system = DefaultPackagingSystem(printWarning: printStderr, which: which(_:)) self.init( options: options, packageId: context.package.id, diff --git a/Plugins/PackageToJS/Templates/index.d.ts b/Plugins/PackageToJS/Templates/index.d.ts index 77d68efd..757a8828 100644 --- a/Plugins/PackageToJS/Templates/index.d.ts +++ b/Plugins/PackageToJS/Templates/index.d.ts @@ -11,7 +11,7 @@ export type Options = { /** * The imports to use for the module */ - imports: Imports + getImports: () => Imports /* #endif */ } diff --git a/Plugins/PackageToJS/Templates/index.js b/Plugins/PackageToJS/Templates/index.js index 76721511..f44dce48 100644 --- a/Plugins/PackageToJS/Templates/index.js +++ b/Plugins/PackageToJS/Templates/index.js @@ -8,7 +8,7 @@ export async function init(_options) { const options = _options || { /* #if HAS_IMPORTS */ /** @returns {import('./instantiate.d').Imports} */ - get imports() { (() => { throw new Error("No imports provided") })() } + getImports() { (() => { throw new Error("No imports provided") })() } /* #endif */ }; let module = options.module; @@ -18,7 +18,7 @@ export async function init(_options) { const instantiateOptions = await defaultBrowserSetup({ module, /* #if HAS_IMPORTS */ - imports: options.imports, + getImports: () => options.getImports(), /* #endif */ /* #if USE_SHARED_MEMORY */ spawnWorker: createDefaultWorkerFactory() diff --git a/Plugins/PackageToJS/Templates/instantiate.d.ts b/Plugins/PackageToJS/Templates/instantiate.d.ts index 86ea6e56..9074d8d2 100644 --- a/Plugins/PackageToJS/Templates/instantiate.d.ts +++ b/Plugins/PackageToJS/Templates/instantiate.d.ts @@ -75,9 +75,13 @@ export type InstantiateOptions = { module: ModuleSource, /* #if HAS_IMPORTS */ /** - * The imports provided by the embedder + * The function to get the imports provided by the embedder */ - imports: Imports, + getImports: (importsContext: { + getInstance: () => WebAssembly.Instance | null, + getExports: () => Exports | null, + _swift: SwiftRuntime, + }) => Imports, /* #endif */ /* #if IS_WASI */ /** diff --git a/Plugins/PackageToJS/Templates/instantiate.js b/Plugins/PackageToJS/Templates/instantiate.js index 65996d86..9b35efd1 100644 --- a/Plugins/PackageToJS/Templates/instantiate.js +++ b/Plugins/PackageToJS/Templates/instantiate.js @@ -23,8 +23,24 @@ import { createInstantiator } from "./bridge-js.js" */ async function createInstantiator(options, swift) { return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => {}, + /** + * @param {WebAssembly.Imports} importObject + * @param {unknown} importsContext + */ + addImports: (importObject, importsContext) => { + // Provide a default implementation for BridgeJS functions that are not + // used at runtime without BridgeJS but required to instantiate the module. + const unexpectedBjsCall = () => { throw new Error("Unexpected call to BridgeJS function") } + importObject["bjs"] = { + swift_js_return_string: unexpectedBjsCall, + swift_js_init_memory: unexpectedBjsCall, + swift_js_make_js_string: unexpectedBjsCall, + swift_js_init_memory_with_result: unexpectedBjsCall, + swift_js_throw: unexpectedBjsCall, + swift_js_retain: unexpectedBjsCall, + swift_js_release: unexpectedBjsCall, + } + }, /** @param {WebAssembly.Instance} instance */ setInstance: (instance) => {}, /** @param {WebAssembly.Instance} instance */ @@ -93,12 +109,13 @@ async function _instantiate( /* #endif */ /* #endif */ }; - instantiator.addImports(importObject); - options.addToCoreImports?.(importObject, { + const importsContext = { getInstance: () => instance, getExports: () => exports, _swift: swift, - }); + }; + instantiator.addImports(importObject, importsContext); + options.addToCoreImports?.(importObject, importsContext); let module; let instance; diff --git a/Plugins/PackageToJS/Templates/platforms/browser.d.ts b/Plugins/PackageToJS/Templates/platforms/browser.d.ts index b851c228..babe3f48 100644 --- a/Plugins/PackageToJS/Templates/platforms/browser.d.ts +++ b/Plugins/PackageToJS/Templates/platforms/browser.d.ts @@ -8,7 +8,7 @@ export function defaultBrowserSetup(options: { onStderrLine?: (line: string) => void, /* #endif */ /* #if HAS_IMPORTS */ - imports: Imports, + getImports: () => Imports, /* #endif */ /* #if USE_SHARED_MEMORY */ spawnWorker: (module: WebAssembly.Module, memory: WebAssembly.Memory, startArg: any) => Worker, diff --git a/Plugins/PackageToJS/Templates/platforms/browser.js b/Plugins/PackageToJS/Templates/platforms/browser.js index 9afd5c94..3fce7c55 100644 --- a/Plugins/PackageToJS/Templates/platforms/browser.js +++ b/Plugins/PackageToJS/Templates/platforms/browser.js @@ -124,7 +124,7 @@ export async function defaultBrowserSetup(options) { return { module: options.module, /* #if HAS_IMPORTS */ - imports: options.imports, + getImports() { return options.getImports() }, /* #endif */ /* #if IS_WASI */ wasi: Object.assign(wasi, { diff --git a/Plugins/PackageToJS/Templates/platforms/browser.worker.js b/Plugins/PackageToJS/Templates/platforms/browser.worker.js index 42fe6a2f..a1ce626d 100644 --- a/Plugins/PackageToJS/Templates/platforms/browser.worker.js +++ b/Plugins/PackageToJS/Templates/platforms/browser.worker.js @@ -13,6 +13,6 @@ self.onmessage = async (event) => { await instantiateForThread(tid, startArg, { ...options, module, memory, - imports: {}, + getImports() { return {} }, }) } diff --git a/Plugins/PackageToJS/Templates/platforms/node.js b/Plugins/PackageToJS/Templates/platforms/node.js index aff708be..4d29fc33 100644 --- a/Plugins/PackageToJS/Templates/platforms/node.js +++ b/Plugins/PackageToJS/Templates/platforms/node.js @@ -65,7 +65,7 @@ export function createDefaultWorkerFactory(preludeScript) { await instantiateForThread(tid, startArg, { ...options, module, memory, - imports: {}, + getImports() { return {} }, }) }) `, @@ -139,7 +139,7 @@ export async function defaultNodeSetup(options) { return { module, - imports: {}, + getImports() { return {} }, /* #if IS_WASI */ wasi: Object.assign(wasi, { setInstance(instance) { diff --git a/Plugins/PackageToJS/Tests/ExampleTests.swift b/Plugins/PackageToJS/Tests/ExampleTests.swift index e635adfc..4de602f1 100644 --- a/Plugins/PackageToJS/Tests/ExampleTests.swift +++ b/Plugins/PackageToJS/Tests/ExampleTests.swift @@ -244,7 +244,7 @@ extension Trait where Self == ConditionTrait { try runProcess(which("npm"), ["install"], [:]) try runProcess(which("npx"), ["playwright", "install", "chromium-headless-shell"], [:]) - try runSwift(["package", "--swift-sdk", swiftSDKID, "js", "test"], [:]) + try runSwift(["package", "--disable-sandbox", "--swift-sdk", swiftSDKID, "js", "test"], [:]) try withTemporaryDirectory(body: { tempDir, _ in let scriptContent = """ const fs = require('fs'); @@ -255,7 +255,10 @@ extension Trait where Self == ConditionTrait { try scriptContent.write(to: tempDir.appending(path: "script.js"), atomically: true, encoding: .utf8) let scriptPath = tempDir.appending(path: "script.js") try runSwift( - ["package", "--swift-sdk", swiftSDKID, "js", "test", "-Xnode=--require=\(scriptPath.path)"], + [ + "package", "--disable-sandbox", "--swift-sdk", swiftSDKID, "js", "test", + "-Xnode=--require=\(scriptPath.path)", + ], [:] ) let testPath = tempDir.appending(path: "test.txt") @@ -265,7 +268,10 @@ extension Trait where Self == ConditionTrait { "test.txt should be created by the script" ) }) - try runSwift(["package", "--swift-sdk", swiftSDKID, "js", "test", "--environment", "browser"], [:]) + try runSwift( + ["package", "--disable-sandbox", "--swift-sdk", swiftSDKID, "js", "test", "--environment", "browser"], + [:] + ) } } diff --git a/Sources/BridgeJSTool/BridgeJSCore b/Sources/BridgeJSTool/BridgeJSCore new file mode 120000 index 00000000..9934baee --- /dev/null +++ b/Sources/BridgeJSTool/BridgeJSCore @@ -0,0 +1 @@ +../../Plugins/BridgeJS/Sources/BridgeJSCore \ No newline at end of file diff --git a/Sources/BridgeJSTool/BridgeJSSkeleton b/Sources/BridgeJSTool/BridgeJSSkeleton new file mode 120000 index 00000000..794c5b08 --- /dev/null +++ b/Sources/BridgeJSTool/BridgeJSSkeleton @@ -0,0 +1 @@ +../../Plugins/BridgeJS/Sources/BridgeJSSkeleton \ No newline at end of file diff --git a/Sources/BridgeJSTool/BridgeJSTool b/Sources/BridgeJSTool/BridgeJSTool new file mode 120000 index 00000000..e92c6fbb --- /dev/null +++ b/Sources/BridgeJSTool/BridgeJSTool @@ -0,0 +1 @@ +../../Plugins/BridgeJS/Sources/BridgeJSTool \ No newline at end of file diff --git a/Sources/BridgeJSTool/BridgeJSUtilities b/Sources/BridgeJSTool/BridgeJSUtilities new file mode 120000 index 00000000..75db3bd9 --- /dev/null +++ b/Sources/BridgeJSTool/BridgeJSUtilities @@ -0,0 +1 @@ +../../Plugins/BridgeJS/Sources/BridgeJSUtilities \ No newline at end of file diff --git a/Sources/BridgeJSTool/README.md b/Sources/BridgeJSTool/README.md new file mode 100644 index 00000000..c3ec3cf3 --- /dev/null +++ b/Sources/BridgeJSTool/README.md @@ -0,0 +1,9 @@ +# BridgeJSTool (Merged Sources) + +This directory contains symlinked sources from `Plugins/BridgeJS` to provide a merged version of the BridgeJSTool for the root Package.swift. + +## Source Merging via Symlinks + +This module uses symlinks to merge multiple modules into a single compilation unit. Compiling multiple modules separately is much slower than compiling them as one merged module. + +Since BridgeJSTool runs during Swift package builds via the BridgeJS plugin, fast compilation is critical for developer experience. The source modules use `#if canImport` directives to work both standalone and when merged here. \ No newline at end of file diff --git a/Sources/BridgeJSTool/TS2Skeleton b/Sources/BridgeJSTool/TS2Skeleton new file mode 120000 index 00000000..c41c1280 --- /dev/null +++ b/Sources/BridgeJSTool/TS2Skeleton @@ -0,0 +1 @@ +../../Plugins/BridgeJS/Sources/TS2Skeleton \ No newline at end of file diff --git a/Sources/JavaScriptEventLoop/JavaScriptEventLoop+LegacyHooks.swift b/Sources/JavaScriptEventLoop/JavaScriptEventLoop+LegacyHooks.swift index bcab9a3d..9353cf34 100644 --- a/Sources/JavaScriptEventLoop/JavaScriptEventLoop+LegacyHooks.swift +++ b/Sources/JavaScriptEventLoop/JavaScriptEventLoop+LegacyHooks.swift @@ -7,9 +7,10 @@ extension JavaScriptEventLoop { static func installByLegacyHook() { #if compiler(>=5.9) - typealias swift_task_asyncMainDrainQueue_hook_Fn = @convention(thin) ( - swift_task_asyncMainDrainQueue_original, swift_task_asyncMainDrainQueue_override - ) -> Void + typealias swift_task_asyncMainDrainQueue_hook_Fn = + @convention(thin) ( + swift_task_asyncMainDrainQueue_original, swift_task_asyncMainDrainQueue_override + ) -> Void let swift_task_asyncMainDrainQueue_hook_impl: swift_task_asyncMainDrainQueue_hook_Fn = { _, _ in swjs_unsafe_event_loop_yield() } @@ -19,7 +20,8 @@ extension JavaScriptEventLoop { ) #endif - typealias swift_task_enqueueGlobal_hook_Fn = @convention(thin) (UnownedJob, swift_task_enqueueGlobal_original) + typealias swift_task_enqueueGlobal_hook_Fn = + @convention(thin) (UnownedJob, swift_task_enqueueGlobal_original) -> Void let swift_task_enqueueGlobal_hook_impl: swift_task_enqueueGlobal_hook_Fn = { job, original in JavaScriptEventLoop.shared.unsafeEnqueue(job) @@ -29,9 +31,10 @@ extension JavaScriptEventLoop { to: UnsafeMutableRawPointer?.self ) - typealias swift_task_enqueueGlobalWithDelay_hook_Fn = @convention(thin) ( - UInt64, UnownedJob, swift_task_enqueueGlobalWithDelay_original - ) -> Void + typealias swift_task_enqueueGlobalWithDelay_hook_Fn = + @convention(thin) ( + UInt64, UnownedJob, swift_task_enqueueGlobalWithDelay_original + ) -> Void let swift_task_enqueueGlobalWithDelay_hook_impl: swift_task_enqueueGlobalWithDelay_hook_Fn = { nanoseconds, job, @@ -45,9 +48,10 @@ extension JavaScriptEventLoop { ) #if compiler(>=5.7) - typealias swift_task_enqueueGlobalWithDeadline_hook_Fn = @convention(thin) ( - Int64, Int64, Int64, Int64, Int32, UnownedJob, swift_task_enqueueGlobalWithDelay_original - ) -> Void + typealias swift_task_enqueueGlobalWithDeadline_hook_Fn = + @convention(thin) ( + Int64, Int64, Int64, Int64, Int32, UnownedJob, swift_task_enqueueGlobalWithDelay_original + ) -> Void let swift_task_enqueueGlobalWithDeadline_hook_impl: swift_task_enqueueGlobalWithDeadline_hook_Fn = { sec, nsec, @@ -64,9 +68,10 @@ extension JavaScriptEventLoop { ) #endif - typealias swift_task_enqueueMainExecutor_hook_Fn = @convention(thin) ( - UnownedJob, swift_task_enqueueMainExecutor_original - ) -> Void + typealias swift_task_enqueueMainExecutor_hook_Fn = + @convention(thin) ( + UnownedJob, swift_task_enqueueMainExecutor_original + ) -> Void let swift_task_enqueueMainExecutor_hook_impl: swift_task_enqueueMainExecutor_hook_Fn = { job, original in JavaScriptEventLoop.shared.unsafeEnqueue(job) } diff --git a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift index 24a9ae48..ee43f87f 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift @@ -1,3 +1,7 @@ +#if hasFeature(Embedded) && os(WASI) +import _Concurrency +#endif + /// A wrapper around [the JavaScript `Promise` class](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise) public final class JSPromise: JSBridgedClass { /// The underlying JavaScript `Promise` object. @@ -66,6 +70,44 @@ public final class JSPromise: JSBridgedClass { self.init(unsafelyWrapping: Self.constructor!.new(closure)) } + #if compiler(>=5.5) && (!hasFeature(Embedded) || os(WASI)) + /// Creates a new `JSPromise` instance from a given async closure. + /// + /// - Parameter body: The async closure to execute. + /// - Returns: A new `JSPromise` instance. + public static func async(body: @escaping @isolated(any) () async throws(JSException) -> Void) -> JSPromise { + self.async { () throws(JSException) -> JSValue in + try await body() + return .undefined + } + } + + /// Creates a new `JSPromise` instance from a given async closure. + /// + /// - Parameter body: The async closure to execute. + /// - Returns: A new `JSPromise` instance. + public static func async(body: @escaping @isolated(any) () async throws(JSException) -> JSValue) -> JSPromise { + JSPromise { resolver in + // NOTE: The context is fully transferred to the unstructured task + // isolation but the compiler can't prove it yet, so we need to + // use `@unchecked Sendable` to make it compile with the Swift 6 mode. + struct Context: @unchecked Sendable { + let resolver: (JSPromise.Result) -> Void + let body: () async throws(JSException) -> JSValue + } + let context = Context(resolver: resolver, body: body) + Task { + do throws(JSException) { + let result = try await context.body() + context.resolver(.success(result)) + } catch { + context.resolver(.failure(error.thrownValue)) + } + } + } + } + #endif + #if !hasFeature(Embedded) public static func resolve(_ value: ConvertibleToJSValue) -> JSPromise { self.init(unsafelyWrapping: Self.constructor!.resolve!(value).object!) diff --git a/Sources/JavaScriptKit/BridgeJSInstrincics.swift b/Sources/JavaScriptKit/BridgeJSInstrincics.swift index 1a8bad7f..56b87fa5 100644 --- a/Sources/JavaScriptKit/BridgeJSInstrincics.swift +++ b/Sources/JavaScriptKit/BridgeJSInstrincics.swift @@ -1,3 +1,8 @@ +/// BridgeJS Intrinsics +/// +/// This file contains low-level intrinsic functions that are used by code generated +/// by the BridgeJS system. + import _CJavaScriptKit #if !arch(wasm32) @@ -6,60 +11,31 @@ import _CJavaScriptKit } #endif -#if arch(wasm32) -@_extern(wasm, module: "bjs", name: "swift_js_return_string") -@_spi(BridgeJS) public func _swift_js_return_string(_ ptr: UnsafePointer?, _ len: Int32) -#else -@_spi(BridgeJS) public func _swift_js_return_string(_ ptr: UnsafePointer?, _ len: Int32) { - _onlyAvailableOnWasm() -} -#endif - -#if arch(wasm32) -@_extern(wasm, module: "bjs", name: "swift_js_init_memory") -@_spi(BridgeJS) public func _swift_js_init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer?) -#else -@_spi(BridgeJS) public func _swift_js_init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer?) { - _onlyAvailableOnWasm() -} -#endif - -#if arch(wasm32) -@_extern(wasm, module: "bjs", name: "swift_js_retain") -@_spi(BridgeJS) public func _swift_js_retain(_ ptr: Int32) -> Int32 -#else -@_spi(BridgeJS) public func _swift_js_retain(_ ptr: Int32) -> Int32 { - _onlyAvailableOnWasm() -} -#endif +// MARK: Exception Handling #if arch(wasm32) @_extern(wasm, module: "bjs", name: "swift_js_throw") @_spi(BridgeJS) public func _swift_js_throw(_ id: Int32) #else +/// Throws a JavaScript exception from Swift code. +/// +/// This function is called by the BridgeJS code generator when a Swift function throws +/// an error. The exception object is retained and stored for later retrieval by the +/// JavaScript-side runtime code. +/// +/// - Parameter id: The ID of the JavaScript exception object to throw @_spi(BridgeJS) public func _swift_js_throw(_ id: Int32) { _onlyAvailableOnWasm() } #endif -#if arch(wasm32) -@_extern(wasm, module: "bjs", name: "swift_js_make_js_string") -@_spi(BridgeJS) public func _swift_js_make_js_string(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 -#else -@_spi(BridgeJS) public func _swift_js_make_js_string(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 { - _onlyAvailableOnWasm() -} -#endif - -#if arch(wasm32) -@_extern(wasm, module: "bjs", name: "swift_js_init_memory_with_result") -@_spi(BridgeJS) public func _swift_js_init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) -#else -@_spi(BridgeJS) public func _swift_js_init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) { - _onlyAvailableOnWasm() -} -#endif - +/// Retrieves and clears any pending JavaScript exception. +/// +/// This function checks for any JavaScript exceptions that were thrown during +/// the execution of imported JavaScript functions. If an exception exists, it +/// is retrieved, cleared from the global storage, and returned as a `JSException`. +/// +/// - Returns: A `JSException` if an exception was pending, `nil` otherwise @_spi(BridgeJS) @_transparent public func _swift_js_take_exception() -> JSException? { #if arch(wasm32) let value = _swift_js_exception_get() @@ -72,3 +48,293 @@ import _CJavaScriptKit _onlyAvailableOnWasm() #endif } + +// MARK: Type lowering/lifting +// +// The following part defines the parameter and return value lowering/lifting +// for a given type. They follow the following pattern: +// +// ```swift +// extension <#TargetType#> { +// // MARK: ImportTS +// @_spi(BridgeJS) public consuming func bridgeJSLowerParameter() -> <#WasmCoreType#> { +// } +// @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ ...) -> <#Self#> { +// } +// // MARK: ExportSwift +// @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ ...) -> <#Self#> { +// } +// @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> <#WasmCoreType#> { +// } +// } +// ``` +// +// where: +// - <#TargetType#>: the higher-level type to be lowered/lifted +// - <#WasmCoreType#>: the corresponding WebAssembly core type or Void +// - `func bridgeJSLowerParameter()`: lower the given higher-level parameter to a WebAssembly core type +// - `func bridgeJSLiftReturn(_ ...) -> <#TargetType#>`: lift the given Wasm core type return value to a higher-level type +// - `func bridgeJSLiftParameter(_ ...) -> <#TargetType#>`: lift the given Wasm core type parameters to a higher-level type +// - `func bridgeJSLowerReturn() -> <#WasmCoreType#>`: lower the given higher-level return value to a Wasm core type +// +// See JSGlueGen.swift in BridgeJSLink for JS-side lowering/lifting implementation. + +/// A protocol that Swift types that can appear as parameters or return values on +public protocol _BridgedSwiftTypeLoweredIntoSingleWasmCoreType { + associatedtype WasmCoreType + // MARK: ImportTS + consuming func bridgeJSLowerParameter() -> WasmCoreType + static func bridgeJSLiftReturn(_ value: WasmCoreType) -> Self + // MARK: ExportSwift + static func bridgeJSLiftParameter(_ value: WasmCoreType) -> Self + consuming func bridgeJSLowerReturn() -> WasmCoreType +} + +extension Bool: _BridgedSwiftTypeLoweredIntoSingleWasmCoreType { + // MARK: ImportTS + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + self ? 1 : 0 + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Bool { + value == 1 + } + // MARK: ExportSwift + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Bool { + value == 1 + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + self ? 1 : 0 + } +} + +extension Int: _BridgedSwiftTypeLoweredIntoSingleWasmCoreType { + // MARK: ImportTS + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + Int32(self) + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Int { + Int(value) + } + // MARK: ExportSwift + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Int { + Int(value) + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + Int32(self) + } +} + +extension Float: _BridgedSwiftTypeLoweredIntoSingleWasmCoreType { + // MARK: ImportTS + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Float32 { + Float32(self) + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Float32) -> Float { + Float(value) + } + // MARK: ExportSwift + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Float32) -> Float { + Float(value) + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Float32 { + Float32(self) + } +} + +extension Double: _BridgedSwiftTypeLoweredIntoSingleWasmCoreType { + // MARK: ImportTS + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Float64 { + Float64(self) + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Float64) -> Double { + Double(value) + } + // MARK: ExportSwift + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Float64) -> Double { + Double(value) + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Float64 { + Float64(self) + } +} + +extension String { + // MARK: ImportTS + + @_spi(BridgeJS) public consuming func bridgeJSLowerParameter() -> Int32 { + #if arch(wasm32) + @_extern(wasm, module: "bjs", name: "swift_js_make_js_string") + func _swift_js_make_js_string(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 + #else + /// Creates a JavaScript string from UTF-8 data in WebAssembly memory + func _swift_js_make_js_string(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 { + _onlyAvailableOnWasm() + } + #endif + return self.withUTF8 { b in + _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + } + + @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ bytesCount: Int32) -> String { + #if arch(wasm32) + @_extern(wasm, module: "bjs", name: "swift_js_init_memory_with_result") + func _swift_js_init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) + #else + /// Initializes WebAssembly memory with result data of JavaScript function call + func _swift_js_init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) { + _onlyAvailableOnWasm() + } + guard #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) else { _onlyAvailableOnWasm() } + #endif + return String(unsafeUninitializedCapacity: Int(bytesCount)) { b in + _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(bytesCount)) + return Int(bytesCount) + } + } + + // MARK: ExportSwift + + @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ bytes: Int32, _ count: Int32) -> String { + #if arch(wasm32) + @_extern(wasm, module: "bjs", name: "swift_js_init_memory") + func _swift_js_init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer?) + #else + /// Initializes a part of WebAssembly memory with Uint8Array a JavaScript object + func _swift_js_init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer?) { + _onlyAvailableOnWasm() + } + guard #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) else { _onlyAvailableOnWasm() } + #endif + return String(unsafeUninitializedCapacity: Int(count)) { b in + _swift_js_init_memory(bytes, b.baseAddress.unsafelyUnwrapped) + return Int(count) + } + } + + @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Void { + #if arch(wasm32) + @_extern(wasm, module: "bjs", name: "swift_js_return_string") + func _swift_js_return_string(_ ptr: UnsafePointer?, _ len: Int32) + #else + /// Write a string to reserved string storage to be returned to JavaScript + func _swift_js_return_string(_ ptr: UnsafePointer?, _ len: Int32) { + _onlyAvailableOnWasm() + } + #endif + return self.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + } +} + +extension JSObject { + // MARK: ImportTS + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + Int32(bitPattern: self.id) + } + @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ id: Int32) -> JSObject { + JSObject(id: JavaScriptObjectRef(bitPattern: id)) + } + + // MARK: ExportSwift + + @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ id: Int32) -> JSObject { + JSObject(id: JavaScriptObjectRef(bitPattern: id)) + } + + @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Int32 { + #if arch(wasm32) + @_extern(wasm, module: "bjs", name: "swift_js_retain") + func _swift_js_retain(_ id: Int32) -> Int32 + #else + /// Retains a JavaScript object reference to ensure the object id + /// remains valid after the function returns + func _swift_js_retain(_ id: Int32) -> Int32 { + _onlyAvailableOnWasm() + } + #endif + return _swift_js_retain(Int32(bitPattern: self.id)) + } +} + +/// A protocol that Swift heap objects exposed to JavaScript via `@JS class` must conform to. +/// +/// The conformance is automatically synthesized by the BridgeJS code generator. +public protocol _BridgedSwiftHeapObject: AnyObject {} + +/// Define the lowering/lifting for `_BridgedSwiftHeapObject` +extension _BridgedSwiftHeapObject { + + // MARK: ImportTS + @available(*, unavailable, message: "Swift heap objects are not supported to be passed to imported JS functions") + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Void {} + @available( + *, + unavailable, + message: "Swift heap objects are not supported to be returned from imported JS functions" + ) + @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ pointer: UnsafeMutableRawPointer) -> Void {} + + // MARK: ExportSwift + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ pointer: UnsafeMutableRawPointer) -> Self { + Unmanaged.fromOpaque(pointer).takeUnretainedValue() + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> UnsafeMutableRawPointer { + // Perform a manual retain on the object, which will be balanced by a release called via FinalizationRegistry + return Unmanaged.passRetained(self).toOpaque() + } +} + +extension _JSBridgedClass { + // MARK: ImportTS + @_spi(BridgeJS) public consuming func bridgeJSLowerParameter() -> Int32 { jsObject.bridgeJSLowerParameter() } + @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ id: Int32) -> Self { + Self(unsafelyWrapping: JSObject.bridgeJSLiftReturn(id)) + } + + // MARK: ExportSwift + @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ id: Int32) -> Self { + Self(unsafelyWrapping: JSObject.bridgeJSLiftParameter(id)) + } + @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Int32 { jsObject.bridgeJSLowerReturn() } +} + +/// A protocol that Swift enum types that do not have a payload can conform to. +/// +/// The conformance is automatically synthesized by the BridgeJS code generator. +public protocol _BridgedSwiftEnumNoPayload {} + +extension _BridgedSwiftEnumNoPayload where Self: RawRepresentable, RawValue == String { + // MARK: ImportTS + @_spi(BridgeJS) public consuming func bridgeJSLowerParameter() -> Int32 { rawValue.bridgeJSLowerParameter() } + @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ bytesCount: Int32) -> Self { + Self(rawValue: .bridgeJSLiftReturn(bytesCount))! + } + + // MARK: ExportSwift + @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ bytes: Int32, _ count: Int32) -> Self { + Self(rawValue: .bridgeJSLiftParameter(bytes, count))! + } + @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Void { rawValue.bridgeJSLowerReturn() } +} + +extension _BridgedSwiftEnumNoPayload +where Self: RawRepresentable, RawValue: _BridgedSwiftTypeLoweredIntoSingleWasmCoreType { + // MARK: ImportTS + @_spi(BridgeJS) public consuming func bridgeJSLowerParameter() -> RawValue.WasmCoreType { + rawValue.bridgeJSLowerParameter() + } + @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ value: RawValue.WasmCoreType) -> Self { + Self(rawValue: .bridgeJSLiftReturn(value))! + } + + // MARK: ExportSwift + @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ value: RawValue.WasmCoreType) -> Self { + Self(rawValue: .bridgeJSLiftParameter(value))! + } + @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> RawValue.WasmCoreType { + rawValue.bridgeJSLowerReturn() + } +} diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/Ahead-of-Time-Code-Generation.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Ahead-of-Time-Code-Generation.md similarity index 100% rename from Sources/JavaScriptKit/Documentation.docc/Articles/Ahead-of-Time-Code-Generation.md rename to Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Ahead-of-Time-Code-Generation.md diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Configuration.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Configuration.md new file mode 100644 index 00000000..835cf6be --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Configuration.md @@ -0,0 +1,62 @@ +# BridgeJS Configuration + +Configure BridgeJS behavior using bridge-js.config.json and bridge-js.config.local.json files. + +## Overview + +> Important: This feature is still experimental. No API stability is guaranteed, and the API may change in future releases. + +BridgeJS supports configuration through JSON configuration files that allow you to customize various aspects of the build process and tool behavior. + +The configuration system supports two complementary files: +- `bridge-js.config.json` - Base configuration (checked into version control) +- `bridge-js.config.local.json` - Local overrides (intended to be ignored by git, for developer-specific settings) + +## Configuration Loading + +### File Locations + +Configuration files should be placed in your Swift package target directory, typically alongside your `bridge-js.d.ts` file: + +``` +Sources/ + YourTarget/ + bridge-js.d.ts + bridge-js.config.json # Base config (commit to git) + bridge-js.config.local.json # Local config (add to .gitignore) + main.swift +``` + +### Loading Order + +BridgeJS loads and merges configuration files in the following order: + +1. **`bridge-js.config.json`** - Base configuration +2. **`bridge-js.config.local.json`** - Local overrides + +Later files override settings from earlier files. This allows teams to commit a base configuration while allowing individual developers to customize their local environment. + +## Configuration Options + +### `tools` + +Specify custom paths for external executables. This is particularly useful when working in environments like Xcode where the system PATH may not be inherited, or when you need to use a specific version of tools for your project. + +Currently supported tools: +- `node` - Node.js runtime (required for TypeScript processing) + +Example: +```json +{ + "tools": { + "node": "/usr/local/bin/node" + } +} +``` + +BridgeJS resolves tool paths in the following priority order: + +1. **Configuration files** (`bridge-js.config.local.json` > `bridge-js.config.json`) +2. **Environment variables** (`JAVASCRIPTKIT_NODE_EXEC`) +3. **System PATH lookup** + diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md new file mode 100644 index 00000000..7c512982 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md @@ -0,0 +1,69 @@ +# Exporting Swift to JavaScript + +Learn how to make your Swift code callable from JavaScript. + +## Overview + +> Important: This feature is still experimental. No API stability is guaranteed, and the API may change in future releases. + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +BridgeJS allows you to expose Swift functions, classes, and methods to JavaScript by using the `@JS` attribute. This enables JavaScript code to call into Swift code running in WebAssembly. + +## Configuring the BridgeJS plugin + +To use the BridgeJS feature, you need to enable the experimental `Extern` feature and add the BridgeJS plugin to your package. Here's an example of a `Package.swift` file: + +```swift +// swift-tools-version:6.0 + +import PackageDescription + +let package = Package( + name: "MyApp", + dependencies: [ + .package(url: "https://github.com/swiftwasm/JavaScriptKit.git", branch: "main") + ], + targets: [ + .executableTarget( + name: "MyApp", + dependencies: ["JavaScriptKit"], + swiftSettings: [ + // This is required because the generated code depends on @_extern(wasm) + .enableExperimentalFeature("Extern") + ], + plugins: [ + // Add build plugin for processing @JS and generate Swift glue code + .plugin(name: "BridgeJS", package: "JavaScriptKit") + ] + ) + ] +) +``` + +The `BridgeJS` plugin will process your Swift code to find declarations marked with `@JS` and generate the necessary bridge code to make them accessible from JavaScript. + +### Building your package for JavaScript + +After configuring your `Package.swift`, you can build your package for JavaScript using the following command: + +```bash +swift package --swift-sdk $SWIFT_SDK_ID js +``` + +This command will: + +1. Process all Swift files with `@JS` annotations +2. Generate JavaScript bindings and TypeScript type definitions (`.d.ts`) for your exported Swift code +3. Output everything to the `.build/plugins/PackageToJS/outputs/` directory + +> Note: For larger projects, you may want to generate the BridgeJS code ahead of time to improve build performance. See for more information. + + + +## Topics + +- +- +- +- diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Class.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Class.md new file mode 100644 index 00000000..d91d0616 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Class.md @@ -0,0 +1,94 @@ +# Exporting Swift Classes to JS + +Learn how to export Swift classes to JavaScript. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +To export a Swift class, mark both the class and any members you want to expose: + +```swift +import JavaScriptKit + +@JS class ShoppingCart { + private var items: [(name: String, price: Double, quantity: Int)] = [] + + @JS init() {} + + @JS public func addItem(name: String, price: Double, quantity: Int) { + items.append((name, price, quantity)) + } + + @JS public func removeItem(atIndex index: Int) { + guard index >= 0 && index < items.count else { return } + items.remove(at: index) + } + + @JS public func getTotal() -> Double { + return items.reduce(0) { $0 + $1.price * Double($1.quantity) } + } + + @JS public func getItemCount() -> Int { + return items.count + } + + // This method won't be accessible from JavaScript (no @JS) + var debugDescription: String { + return "Cart with \(items.count) items, total: \(getTotal())" + } +} +``` + +In JavaScript: + +```javascript +import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; +const { exports } = await init({}); + +const cart = new exports.ShoppingCart(); +cart.addItem("Laptop", 999.99, 1); +cart.addItem("Mouse", 24.99, 2); +console.log(`Items in cart: ${cart.getItemCount()}`); +console.log(`Total: $${cart.getTotal().toFixed(2)}`); +``` + +The generated TypeScript declarations for this class would look like: + +```typescript +// Base interface for Swift reference types +export interface SwiftHeapObject { + release(): void; +} + +// ShoppingCart interface with all exported methods +export interface ShoppingCart extends SwiftHeapObject { + addItem(name: string, price: number, quantity: number): void; + removeItem(atIndex: number): void; + getTotal(): number; + getItemCount(): number; +} + +export type Exports = { + ShoppingCart: { + new(): ShoppingCart; + } +} +``` + +## Supported Features + +| Swift Feature | Status | +|:--------------|:-------| +| Initializers: `init()` | ✅ | +| Initializers that throw JSException: `init() throws(JSException)` | ✅ | +| Initializers that throw any exception: `init() throws` | ❌ | +| Async initializers: `init() async` | ❌ | +| Deinitializers: `deinit` | ✅ | +| Stored properties: `var`, `let` (with `willSet`, `didSet`) | ✅ | +| Computed properties: `var x: X { get set }` | ✅ | +| Computed properties with effects: `var x: X { get async throws }` | 🚧 | +| Methods: `func` | ✅ (See ) | +| Static/class methods: `static func`, `class func` | 🚧 | +| Subscripts: `subscript()` | ❌ | +| Generics | ❌ | diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Enum.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Enum.md new file mode 100644 index 00000000..e4bc1d7f --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Enum.md @@ -0,0 +1,408 @@ +# Exporting Swift Enums to JS + +Learn how to export Swift enums to JavaScript. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +BridgeJS supports two output styles for enums, controlled by the `enumStyle` parameter: + +- **`.const` (default)**: Generates const objects with union types +- **`.tsEnum`**: Generates native TypeScript enum declarations - **only available for case enums and raw value enums with String or numeric raw types** + +Examples output of both styles can be found below. + +#### Case Enums + +**Swift Definition:** + +```swift +@JS enum Direction { + case north + case south + case east + case west +} + +@JS(enumStyle: .tsEnum) enum TSDirection { + case north + case south + case east + case west +} + +@JS enum Status { + case loading + case success + case error +} +``` + +**Generated TypeScript Declaration:** + +```typescript +// Const object style (default) +const Direction: { + readonly North: 0; + readonly South: 1; + readonly East: 2; + readonly West: 3; +}; +type Direction = typeof Direction[keyof typeof Direction]; + +// Native TypeScript enum style +enum TSDirection { + North = 0, + South = 1, + East = 2, + West = 3, +} + +const Status: { + readonly Loading: 0; + readonly Success: 1; + readonly Error: 2; +}; +type Status = typeof Status[keyof typeof Status]; +``` + +**Usage in TypeScript:** + +```typescript +const direction: Direction = exports.Direction.North; +const tsDirection: TSDirection = exports.TSDirection.North; +const status: Status = exports.Status.Loading; + +exports.setDirection(exports.Direction.South); +exports.setTSDirection(exports.TSDirection.East); +const currentDirection: Direction = exports.getDirection(); +const currentTSDirection: TSDirection = exports.getTSDirection(); + +const result: Status = exports.processDirection(exports.Direction.East); + +function handleDirection(direction: Direction) { + switch (direction) { + case exports.Direction.North: + console.log("Going north"); + break; + case exports.Direction.South: + console.log("Going south"); + break; + // TypeScript will warn about missing cases + } +} +``` + +BridgeJS also generates convenience initializers and computed properties for each case-style enum, allowing the rest of the Swift glue code to remain minimal and consistent. This avoids repetitive switch statements in every function that passes enum values between JavaScript and Swift. + +```swift +extension Direction { + init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .north + case 1: + self = .south + case 2: + self = .east + case 3: + self = .west + default: + return nil + } + } + + var bridgeJSRawValue: Int32 { + switch self { + case .north: + return 0 + case .south: + return 1 + case .east: + return 2 + case .west: + return 3 + } + } +} +... +@_expose(wasm, "bjs_setDirection") +@_cdecl("bjs_setDirection") +public func _bjs_setDirection(direction: Int32) -> Void { + #if arch(wasm32) + setDirection(_: Direction(bridgeJSRawValue: direction)!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getDirection") +@_cdecl("bjs_getDirection") +public func _bjs_getDirection() -> Int32 { + #if arch(wasm32) + let ret = getDirection() + return ret.bridgeJSRawValue + #else + fatalError("Only available on WebAssembly") + #endif +} +``` + +#### Raw Value Enums + +##### String Raw Values + +**Swift Definition:** + +```swift +// Default const object style +@JS enum Theme: String { + case light = "light" + case dark = "dark" + case auto = "auto" +} + +// Native TypeScript enum style +@JS(enumStyle: .tsEnum) enum TSTheme: String { + case light = "light" + case dark = "dark" + case auto = "auto" +} +``` + +**Generated TypeScript Declaration:** + +```typescript +// Const object style (default) +const Theme: { + readonly Light: "light"; + readonly Dark: "dark"; + readonly Auto: "auto"; +}; +type Theme = typeof Theme[keyof typeof Theme]; + +// Native TypeScript enum style +enum TSTheme { + Light = "light", + Dark = "dark", + Auto = "auto", +} +``` + +**Usage in TypeScript:** + +```typescript +// Both styles work similarly in usage +const theme: Theme = exports.Theme.Dark; +const tsTheme: TSTheme = exports.TSTheme.Dark; + +exports.setTheme(exports.Theme.Light); +const currentTheme: Theme = exports.getTheme(); + +const status: HttpStatus = exports.processTheme(exports.Theme.Auto); +``` + +##### Integer Raw Values + +**Swift Definition:** + +```swift +// Default const object style +@JS enum HttpStatus: Int { + case ok = 200 + case notFound = 404 + case serverError = 500 +} + +// Native TypeScript enum style +@JS(enumStyle: .tsEnum) enum TSHttpStatus: Int { + case ok = 200 + case notFound = 404 + case serverError = 500 +} + +@JS enum Priority: Int32 { + case lowest = 1 + case low = 2 + case medium = 3 + case high = 4 + case highest = 5 +} +``` + +**Generated TypeScript Declaration:** + +```typescript +// Const object style (default) +const HttpStatus: { + readonly Ok: 200; + readonly NotFound: 404; + readonly ServerError: 500; +}; +type HttpStatus = typeof HttpStatus[keyof typeof HttpStatus]; + +// Native TypeScript enum style +enum TSHttpStatus { + Ok = 200, + NotFound = 404, + ServerError = 500, +} + +const Priority: { + readonly Lowest: 1; + readonly Low: 2; + readonly Medium: 3; + readonly High: 4; + readonly Highest: 5; +}; +type Priority = typeof Priority[keyof typeof Priority]; +``` + +**Usage in TypeScript:** + +```typescript +const status: HttpStatus = exports.HttpStatus.Ok; +const tsStatus: TSHttpStatus = exports.TSHttpStatus.Ok; +const priority: Priority = exports.Priority.High; + +exports.setHttpStatus(exports.HttpStatus.NotFound); +exports.setPriority(exports.Priority.Medium); + +const convertedPriority: Priority = exports.convertPriority(exports.HttpStatus.Ok); +``` + +### Namespace Enums + +Namespace enums are empty enums (containing no cases) used for organizing related types and functions into hierarchical namespaces. + +**Swift Definition:** + +```swift +@JS enum Utils { + @JS class Converter { + @JS init() {} + + @JS func toString(value: Int) -> String { + return String(value) + } + } +} + +// Nested namespace enums with no @JS(namespace:) macro used +@JS enum Networking { + @JS enum API { + @JS enum Method { + case get + case post + } + + @JS class HTTPServer { + @JS init() {} + @JS func call(_ method: Method) + } + } +} + +// Top level enum can still use explicit namespace via @JS(namespace:) +@JS(namespace: "Networking.APIV2") +enum Internal { + @JS enum SupportedMethod { + case get + case post + } + + @JS class TestServer { + @JS init() {} + @JS func call(_ method: SupportedMethod) + } +} +``` + +**Generated TypeScript Declaration:** + +```typescript +declare global { + namespace Utils { + class Converter { + constructor(); + toString(value: number): string; + } + } + namespace Networking { + namespace API { + class HTTPServer { + constructor(); + call(method: Networking.API.Method): void; + } + const Method: { + readonly Get: 0; + readonly Post: 1; + }; + type Method = typeof Method[keyof typeof Method]; + } + namespace APIV2 { + namespace Internal { + class TestServer { + constructor(); + call(method: Internal.SupportedMethod): void; + } + const SupportedMethod: { + readonly Get: 0; + readonly Post: 1; + }; + type SupportedMethod = typeof SupportedMethod[keyof typeof SupportedMethod]; + } + } + } +} +``` + +**Usage in TypeScript:** + +```typescript +// Access nested classes through namespaces +const converter = new globalThis.Utils.Converter(); +const result: string = converter.toString(42) + +const server = new globalThis.Networking.API.HTTPServer(); +const method: Networking.API.Method = globalThis.Networking.API.Method.Get; +server.call(method) + +const testServer = new globalThis.Networking.APIV2.Internal.TestServer(); +const supportedMethod: Internal.SupportedMethod = globalThis.Networking.APIV2.Internal.SupportedMethod.Post; +testServer.call(supportedMethod); +``` + +Things to remember when using enums for namespacing: + +1. Only enums with no cases will be used for namespaces +2. Top-level enums can use `@JS(namespace: "Custom.Path")` to place themselves in custom namespaces, which will be used as "base namespace" for all nested elements as well +3. Classes and enums nested within namespace enums **cannot** use `@JS(namespace:)` - this would create conflicting namespace declarations + +**Invalid Usage:** + +```swift +@JS enum Utils { + // Invalid - nested items cannot specify their own namespace + @JS(namespace: "Custom") class Helper { + @JS init() {} + } +} +``` + +**Valid Usage:** + +```swift +// Valid - top-level enum with explicit namespace +@JS(namespace: "Custom.Utils") +enum Helper { + @JS class Converter { + @JS init() {} + } +} +``` + +#### Associated Value Enums + +Associated value enums are not currently supported, but are planned for future releases. diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md new file mode 100644 index 00000000..fff45f11 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md @@ -0,0 +1,153 @@ +# Exporting Swift Functions to JS + +Learn how to export Swift functions to JavaScript. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +To export a Swift function to JavaScript, mark it with the `@JS` attribute and make it `public`: + +```swift +import JavaScriptKit + +@JS public func calculateTotal(price: Double, quantity: Int) -> Double { + return price * Double(quantity) +} + +@JS public func formatCurrency(amount: Double) -> String { + return "$\(String(format: "%.2f", amount))" +} +``` + +These functions will be accessible from JavaScript: + +```javascript +const total = exports.calculateTotal(19.99, 3); +const formattedTotal = exports.formatCurrency(total); +console.log(formattedTotal); // "$59.97" +``` + +The generated TypeScript declarations for these functions would look like: + +```typescript +export type Exports = { + calculateTotal(price: number, quantity: number): number; + formatCurrency(amount: number): string; +} +``` + +### Throwing functions + +Swift functions can throw JavaScript errors using `throws(JSException)`. + +```swift +import JavaScriptKit + +@JS public func findUser(id: Int) throws(JSException) -> String { + if id <= 0 { + throw JSException(JSError(message: "Invalid ID").jsValue) + } + return "User_\(id)" +} +``` + +From JavaScript, call with `try/catch`: + +```javascript +try { + const name = exports.findUser(42); + console.log(name); +} catch (e) { + console.error("findUser failed:", e); +} +``` + +Generated TypeScript type: + +```typescript +export type Exports = { + findUser(id: number): string; // throws at runtime +} +``` + +Notes: +- Only `throws(JSException)` is supported. Plain `throws` is not supported. +- Thrown values are surfaced to JS as normal JS exceptions. + +### Async functions + +Async Swift functions are exposed as Promise-returning JS functions. + +```swift +import JavaScriptKit + +@JS public func fetchCount(endpoint: String) async -> Int { + // Simulate async work + try? await Task.sleep(nanoseconds: 50_000_000) + return endpoint.count +} +``` + +Usage from JavaScript: + +```javascript +const count = await exports.fetchCount("/items"); +``` + +Generated TypeScript type: + +```typescript +export type Exports = { + fetchCount(endpoint: string): Promise; +} +``` + +### Async + throws + +Async throwing functions become Promise-returning JS functions that reject on error. + +```swift +import JavaScriptKit + +@JS public func loadProfile(userId: Int) async throws(JSException) -> String { + if userId <= 0 { throw JSException(JSError(message: "Bad userId").jsValue) } + try? await Task.sleep(nanoseconds: 50_000_000) + return "Profile_\(userId)" +} +``` + +JavaScript usage: + +```javascript +try { + const profile = await exports.loadProfile(1); + console.log(profile); +} catch (e) { + console.error("loadProfile failed:", e); +} +``` + +TypeScript: + +```typescript +export type Exports = { + loadProfile(userId: number): Promise; +} +``` + +## Supported Features + +| Swift Feature | Status | +|:--------------|:-------| +| Primitive parameter/result types: (e.g. `Bool`, `Int`, `Double`) | ✅ | +| `String` parameter/result type | ✅ | +| `@JS class` parameter/result type | ✅ | +| `@JS enum` parameter/result type | ✅ | +| `JSObject` parameter/result type | ✅ | +| Throwing JS exception: `func x() throws(JSException)` | ✅ | +| Throwing any exception: `func x() throws` | ❌ | +| Async methods: `func x() async` | ✅ | +| Generics | ❌ | +| Opaque types: `func x() -> some P`, `func y(_: some P)` | ❌ | +| Default parameter values: `func x(_ foo: String = "")` | ❌ | \ No newline at end of file diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Using-Namespace.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Using-Namespace.md new file mode 100644 index 00000000..2026c6a2 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Using-Namespace.md @@ -0,0 +1,95 @@ +# Using Namespaces + +Learn how to organize exported Swift code into JavaScript namespaces. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +The `@JS` macro supports organizing your exported Swift code into namespaces using dot-separated strings. This allows you to create hierarchical structures in JavaScript that mirror your Swift code organization. + +### Functions with Namespaces + +You can export functions to specific namespaces by providing a namespace parameter: + +```swift +import JavaScriptKit + +// Export a function to a custom namespace +@JS(namespace: "MyModule.Utils") func namespacedFunction() -> String { + return "namespaced" +} +``` + +This function will be accessible in JavaScript through its namespace hierarchy: + +```javascript +// Access the function through its namespace +const result = globalThis.MyModule.Utils.namespacedFunction(); +console.log(result); // "namespaced" +``` + +The generated TypeScript declaration will reflect the namespace structure: + +```typescript +declare global { + namespace MyModule { + namespace Utils { + function namespacedFunction(): string; + } + } +} +``` + +### Classes with Namespaces + +For classes, you only need to specify the namespace on the top-level class declaration. All exported methods within the class will be part of that namespace: + +```swift +import JavaScriptKit + +@JS(namespace: "__Swift.Foundation") class Greeter { + var name: String + + @JS init(name: String) { + self.name = name + } + + @JS func greet() -> String { + return "Hello, " + self.name + "!" + } + + func changeName(name: String) { + self.name = name + } +} +``` + +In JavaScript, this class is accessible through its namespace: + +```javascript +// Create instances through namespaced constructors +const greeter = new globalThis.__Swift.Foundation.Greeter("World"); +console.log(greeter.greet()); // "Hello, World!" +``` + +The generated TypeScript declaration will organize the class within its namespace: + +```typescript +declare global { + namespace __Swift { + namespace Foundation { + class Greeter { + constructor(name: string); + greet(): string; + } + } + } +} + +export interface Greeter extends SwiftHeapObject { + greet(): string; +} +``` + +Using namespaces can be preferable for projects with many global functions, as they help prevent naming collisions. Namespaces also provide intuitive hierarchies for organizing your exported Swift code, and they do not affect the code generated by `@JS` declarations without namespaces. \ No newline at end of file diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/Importing-TypeScript-into-Swift.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript-into-Swift.md similarity index 91% rename from Sources/JavaScriptKit/Documentation.docc/Articles/Importing-TypeScript-into-Swift.md rename to Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript-into-Swift.md index 98a9c80c..b091f714 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/Importing-TypeScript-into-Swift.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript-into-Swift.md @@ -6,6 +6,8 @@ Learn how to leverage TypeScript definitions to create type-safe bindings for Ja > Important: This feature is still experimental. No API stability is guaranteed, and the API may change in future releases. +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + BridgeJS enables seamless integration between Swift and JavaScript by automatically generating Swift bindings from TypeScript declaration files (`.d.ts`). This provides type-safe access to JavaScript APIs directly from your Swift code. The key benefits of this approach over `@dynamicMemberLookup`-based APIs include: @@ -62,7 +64,7 @@ interface Document { // Properties title: string; readonly body: HTMLElement; - + // Methods getElementById(id: string): HTMLElement; createElement(tagName: string): HTMLElement; @@ -96,7 +98,7 @@ struct Document { struct HTMLElement { var innerText: String { get set } var className: String { get set } - + func appendChild(_ child: HTMLElement) } @@ -161,14 +163,23 @@ import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; // Initialize the WebAssembly module with JavaScript implementations const { exports } = await init({ - imports: { - consoleLog: (message) => { - console.log(message); - }, - getDocument: () => document, + getImports() { + return { + consoleLog: (message) => { + console.log(message); + }, + getDocument: () => document, + } } }); // Call the entry point of your Swift application exports.run(); ``` + +## Topics + +- +- +- +- diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Class.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Class.md new file mode 100644 index 00000000..e04bb9e7 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Class.md @@ -0,0 +1,64 @@ +# Importing TypeScript Classes into Swift + +Learn how TypeScript classes map to Swift when importing APIs. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +BridgeJS reads class declarations in your `bridge-js.d.ts` and generates Swift structs that represent JS objects. Constructors, methods, and properties are bridged via thunks that call into your JavaScript implementations at runtime. + +## Example + +TypeScript definition (`bridge-js.d.ts`): + +```typescript +export class Greeter { + readonly id: string; + message: string; + constructor(id: string, name: string); + greet(): string; +} +``` + +Generated Swift API: + +```swift +struct Greeter { + init(id: String, name: String) throws(JSException) + + // Properties + // Readonly property + var id: String { get throws(JSException) } + // Writable property + var message: String { get throws(JSException) } + func setMessage(_ newValue: String) throws(JSException) + + // Methods + func greet() throws(JSException) -> String +} +``` + +Notes: +- Property setters are emitted as `set(_:)` functions, not Swift `set` accessors since `set` accessors can't have `throws` +- All thunks throw `JSException` if the underlying JS throws. + +JavaScript implementation wiring (provided by your app): + +```javascript +// index.js +import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; + +class Greeter { + readonly id: string; + message: string; + constructor(id: string, name: string) { ... } + greet(): string { ... } +} + +const { exports } = await init({ + getImports() { + return { Greeter }; + } +}); +``` diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Function.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Function.md new file mode 100644 index 00000000..060ac390 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Function.md @@ -0,0 +1,60 @@ +# Importing TypeScript Functions into Swift + +Learn how functions declared in TypeScript become callable Swift functions. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +BridgeJS reads your `bridge-js.d.ts` and generates Swift thunks that call into JavaScript implementations provided at runtime. Each imported function becomes a top-level Swift function with the same name and a throwing signature. + +### Example + +TypeScript definition (`bridge-js.d.ts`): + +```typescript +export function add(a: number, b: number): number; +export function setTitle(title: string): void; +export function fetchUser(id: string): Promise; +``` + +Generated Swift signatures: + +```swift +func add(a: Double, b: Double) throws(JSException) -> Double +func setTitle(title: String) throws(JSException) +func fetchUser(id: String) throws(JSException) -> JSPromise +``` + +JavaScript implementation wiring (provided by your app): + +```javascript +// index.js +import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; + +const { exports } = await init({ + getImports() { + return { + add: (a, b) => a + b, + setTitle: (title) => { document.title = title }, + fetchUser: (id) => fetch(`/api/users/${id}`).then(r => r.json()), + }; + } +}); +``` + +### Error handling + +- All imported Swift functions are generated as `throws(JSException)` and will throw if the underlying JS implementation throws. + +## Supported features + +| Feature | Status | +|:--|:--| +| Primitive parameter/result types: (e.g. `boolean`, `number`) | ✅ | +| `string` parameter/result type | ✅ | +| Enums in signatures | ❌ | +| Async function | ✅ | +| Generics | ❌ | + + diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Interface.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Interface.md new file mode 100644 index 00000000..0e0aed0d --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Interface.md @@ -0,0 +1,34 @@ +# Importing TypeScript Interfaces into Swift + +Learn how TypeScript interfaces become Swift value types with methods and properties. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +BridgeJS converts TS interfaces to Swift structs conforming to an internal bridging protocol and provides thunks for methods and properties that call into your JavaScript implementations. + +> Note: Interfaces are bridged very similarly to classes. Methods and properties map the same way. See for more details. + +### Example + +TypeScript definition (`bridge-js.d.ts`): + +```typescript +export interface HTMLElement { + readonly innerText: string; + className: string; + appendChild(child: HTMLElement): void; +} +``` + +Generated Swift API: + +```swift +struct HTMLElement { + var innerText: String { get throws(JSException) } + var className: String { get throws(JSException) } + func setClassName(_ newValue: String) throws(JSException) + func appendChild(_ child: HTMLElement) throws(JSException) +} +``` diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-TypeAlias.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-TypeAlias.md new file mode 100644 index 00000000..b5242ee3 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-TypeAlias.md @@ -0,0 +1,47 @@ +# Importing TypeScript Type Aliases into Swift + +Understand how TypeScript type aliases are handled when generating Swift bindings. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +BridgeJS resolves TypeScript aliases while importing. If an alias names an anonymous object type, Swift will generate a corresponding bridged struct using that name. + +> Note: When a type alias names an anonymous object type, its bridging behavior (constructors not applicable, but methods/properties if referenced) mirrors class/interface importing. See for more details. + +### Examples + +```typescript +// Primitive alias → maps to the underlying primitive +export type Price = number; + +// Object-shaped alias with a name → becomes a named bridged type when referenced +export type User = { + readonly id: string; + name: string; + age: Price; +} + +export function getUser(): User; +``` + +Generated Swift (simplified): + +```swift +// Price → Double + +struct User { + // Readonly property + var id: String { get throws(JSException) } + + // Writable properties + var name: String { get throws(JSException) } + func setName(_ newValue: String) throws(JSException) + + var age: Double { get throws(JSException) } + func setAge(_ newValue: Double) throws(JSException) +} + +func getUser() throws(JSException) -> User +``` diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md new file mode 100644 index 00000000..71b92573 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md @@ -0,0 +1,18 @@ +# TypeScript and Swift Type Mapping + +Use this page as a quick reference for how common TypeScript types appear in Swift when importing, and how exported Swift types surface on the TypeScript side. + +## Type mapping + +| TypeScript type | Swift type | +|:--|:--| +| `number` | `Double` | +| `string` | `String` | +| `boolean` | `Bool` | +| TODO | `Array` | +| TODO | `Dictionary` | +| TODO | `Optional` | +| `T \| undefined` | TODO | +| `Promise` | `JSPromise` | +| `any` / `unknown` / `object` | `JSObject` | +| Other types | `JSObject` | diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md b/Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md deleted file mode 100644 index 6ce30772..00000000 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md +++ /dev/null @@ -1,254 +0,0 @@ -# Exporting Swift to JavaScript - -Learn how to make your Swift code callable from JavaScript. - -## Overview - -> Important: This feature is still experimental. No API stability is guaranteed, and the API may change in future releases. - -BridgeJS allows you to expose Swift functions, classes, and methods to JavaScript by using the `@JS` attribute. This enables JavaScript code to call into Swift code running in WebAssembly. - -## Configuring the BridgeJS plugin - -To use the BridgeJS feature, you need to enable the experimental `Extern` feature and add the BridgeJS plugin to your package. Here's an example of a `Package.swift` file: - -```swift -// swift-tools-version:6.0 - -import PackageDescription - -let package = Package( - name: "MyApp", - dependencies: [ - .package(url: "https://github.com/swiftwasm/JavaScriptKit.git", branch: "main") - ], - targets: [ - .executableTarget( - name: "MyApp", - dependencies: ["JavaScriptKit"], - swiftSettings: [ - // This is required because the generated code depends on @_extern(wasm) - .enableExperimentalFeature("Extern") - ], - plugins: [ - // Add build plugin for processing @JS and generate Swift glue code - .plugin(name: "BridgeJS", package: "JavaScriptKit") - ] - ) - ] -) -``` - -The `BridgeJS` plugin will process your Swift code to find declarations marked with `@JS` and generate the necessary bridge code to make them accessible from JavaScript. - -### Building your package for JavaScript - -After configuring your `Package.swift`, you can build your package for JavaScript using the following command: - -```bash -swift package --swift-sdk $SWIFT_SDK_ID js -``` - -This command will: -1. Process all Swift files with `@JS` annotations -2. Generate JavaScript bindings and TypeScript type definitions (`.d.ts`) for your exported Swift code -4. Output everything to the `.build/plugins/PackageToJS/outputs/` directory - -> Note: For larger projects, you may want to generate the BridgeJS code ahead of time to improve build performance. See for more information. - -## Marking Swift Code for Export - -### Functions - -To export a Swift function to JavaScript, mark it with the `@JS` attribute and make it `public`: - -```swift -import JavaScriptKit - -@JS public func calculateTotal(price: Double, quantity: Int) -> Double { - return price * Double(quantity) -} - -@JS public func formatCurrency(amount: Double) -> String { - return "$\(String(format: "%.2f", amount))" -} -``` - -These functions will be accessible from JavaScript: - -```javascript -const total = exports.calculateTotal(19.99, 3); -const formattedTotal = exports.formatCurrency(total); -console.log(formattedTotal); // "$59.97" -``` - -The generated TypeScript declarations for these functions would look like: - -```typescript -export type Exports = { - calculateTotal(price: number, quantity: number): number; - formatCurrency(amount: number): string; -} -``` - -### Classes - -To export a Swift class, mark both the class and any members you want to expose: - -```swift -import JavaScriptKit - -@JS class ShoppingCart { - private var items: [(name: String, price: Double, quantity: Int)] = [] - - @JS init() {} - - @JS public func addItem(name: String, price: Double, quantity: Int) { - items.append((name, price, quantity)) - } - - @JS public func removeItem(atIndex index: Int) { - guard index >= 0 && index < items.count else { return } - items.remove(at: index) - } - - @JS public func getTotal() -> Double { - return items.reduce(0) { $0 + $1.price * Double($1.quantity) } - } - - @JS public func getItemCount() -> Int { - return items.count - } - - // This method won't be accessible from JavaScript (no @JS) - var debugDescription: String { - return "Cart with \(items.count) items, total: \(getTotal())" - } -} -``` - -In JavaScript: - -```javascript -import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; -const { exports } = await init({}); - -const cart = new exports.ShoppingCart(); -cart.addItem("Laptop", 999.99, 1); -cart.addItem("Mouse", 24.99, 2); -console.log(`Items in cart: ${cart.getItemCount()}`); -console.log(`Total: $${cart.getTotal().toFixed(2)}`); -``` - -The generated TypeScript declarations for this class would look like: - -```typescript -// Base interface for Swift reference types -export interface SwiftHeapObject { - release(): void; -} - -// ShoppingCart interface with all exported methods -export interface ShoppingCart extends SwiftHeapObject { - addItem(name: string, price: number, quantity: number): void; - removeItem(atIndex: number): void; - getTotal(): number; - getItemCount(): number; -} - -export type Exports = { - ShoppingCart: { - new(): ShoppingCart; - } -} -``` - -## Using Namespaces - -The `@JS` macro supports organizing your exported Swift code into namespaces using dot-separated strings. This allows you to create hierarchical structures in JavaScript that mirror your Swift code organization. - -### Functions with Namespaces - -You can export functions to specific namespaces by providing a namespace parameter: - -```swift -import JavaScriptKit - -// Export a function to a custom namespace -@JS(namespace: "MyModule.Utils") func namespacedFunction() -> String { - return "namespaced" -} -``` - -This function will be accessible in JavaScript through its namespace hierarchy: - -```javascript -// Access the function through its namespace -const result = globalThis.MyModule.Utils.namespacedFunction(); -console.log(result); // "namespaced" -``` - -The generated TypeScript declaration will reflect the namespace structure: - -```typescript -declare global { - namespace MyModule { - namespace Utils { - function namespacedFunction(): string; - } - } -} -``` - -### Classes with Namespaces - -For classes, you only need to specify the namespace on the top-level class declaration. All exported methods within the class will be part of that namespace: - -```swift -import JavaScriptKit - -@JS(namespace: "__Swift.Foundation") class Greeter { - var name: String - - @JS init(name: String) { - self.name = name - } - - @JS func greet() -> String { - return "Hello, " + self.name + "!" - } - - func changeName(name: String) { - self.name = name - } -} -``` - -In JavaScript, this class is accessible through its namespace: - -```javascript -// Create instances through namespaced constructors -const greeter = new globalThis.__Swift.Foundation.Greeter("World"); -console.log(greeter.greet()); // "Hello, World!" -``` - -The generated TypeScript declaration will organize the class within its namespace: - -```typescript -declare global { - namespace __Swift { - namespace Foundation { - class Greeter { - constructor(name: string); - greet(): string; - } - } - } -} - -export interface Greeter extends SwiftHeapObject { - greet(): string; -} -``` - -Using namespaces can be preferable for projects with many global functions, as they help prevent naming collisions. Namespaces also provide intuitive hierarchies for organizing your exported Swift code, and they do not affect the code generated by `@JS` declarations without namespaces. diff --git a/Sources/JavaScriptKit/Documentation.docc/Documentation.md b/Sources/JavaScriptKit/Documentation.docc/Documentation.md index ffc16843..13a6a321 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Documentation.md +++ b/Sources/JavaScriptKit/Documentation.docc/Documentation.md @@ -51,14 +51,19 @@ Check out the [examples](https://github.com/swiftwasm/JavaScriptKit/tree/main/Ex ### Articles -- -- - -- - +### BridgeJS + +- +- +- +- +- + ### Core APIs - ``JSValue`` - ``JSObject`` -- ``JS()`` +- ``JS(namespace:enumStyle:)`` diff --git a/Sources/JavaScriptKit/JSBridgedType.swift b/Sources/JavaScriptKit/JSBridgedType.swift index 92739079..ffa65f4a 100644 --- a/Sources/JavaScriptKit/JSBridgedType.swift +++ b/Sources/JavaScriptKit/JSBridgedType.swift @@ -13,8 +13,21 @@ extension JSBridgedType { public var description: String { jsValue.description } } +/// A protocol that Swift classes that are exposed to JavaScript via `@JS class` conform to. +/// +/// The conformance is automatically synthesized by the BridgeJS code generator. +public protocol _JSBridgedClass { + /// The JavaScript object wrapped by this instance. + /// You may assume that `jsObject instanceof Self.constructor == true` + var jsObject: JSObject { get } + + /// Create an instance wrapping the given JavaScript object. + /// You may assume that `jsObject instanceof Self.constructor` + init(unsafelyWrapping jsObject: JSObject) +} + /// Conform to this protocol when your Swift class wraps a JavaScript class. -public protocol JSBridgedClass: JSBridgedType { +public protocol JSBridgedClass: JSBridgedType, _JSBridgedClass { /// The constructor function for the JavaScript class static var constructor: JSFunction? { get } diff --git a/Sources/JavaScriptKit/Macros.swift b/Sources/JavaScriptKit/Macros.swift index dac264ff..b8a44a08 100644 --- a/Sources/JavaScriptKit/Macros.swift +++ b/Sources/JavaScriptKit/Macros.swift @@ -1,3 +1,11 @@ +/// Controls how Swift enums annotated with `@JS` are emitted to TypeScript. +/// - `const`: Emit the current BridgeJS style: a `const` object with literal members plus a type alias. +/// - `tsEnum`: Emit a TypeScript `enum` declaration (only valid for simple enums and raw-value enums with String or numeric raw types). +public enum JSEnumStyle: String { + case const + case tsEnum +} + /// A macro that exposes Swift functions, classes, and methods to JavaScript. /// /// Apply this macro to Swift declarations that you want to make callable from JavaScript: @@ -90,7 +98,12 @@ /// /// - Parameter namespace: A dot-separated string that defines the namespace hierarchy in JavaScript. /// Each segment becomes a nested object in the resulting JavaScript structure. +/// - Parameter enumStyle: Controls how enums are emitted to TypeScript for this declaration: +/// use `.const` (default) to emit a const object + type alias, +/// or `.tsEnum` to emit a TypeScript `enum`. +/// `.tsEnum` is supported for case enums and raw-value enums with String or numeric raw types. +/// Bool raw-value enums are not supported with `.tsEnum` and will produce an error. /// /// - Important: This feature is still experimental. No API stability is guaranteed, and the API may change in future releases. @attached(peer) -public macro JS(namespace: String? = nil) = Builtin.ExternalMacro +public macro JS(namespace: String? = nil, enumStyle: JSEnumStyle = .const) = Builtin.ExternalMacro diff --git a/Sources/_CJavaScriptKit/include/BridgeJSInstrincics.h b/Sources/_CJavaScriptKit/include/BridgeJSInstrincics.h index 4f5ba93c..7ca09860 100644 --- a/Sources/_CJavaScriptKit/include/BridgeJSInstrincics.h +++ b/Sources/_CJavaScriptKit/include/BridgeJSInstrincics.h @@ -5,6 +5,8 @@ #include "WasmGlobalMacros.h" #if __wasm__ +// Global thread-local pointer storage for temporarily storing JS exceptions +// thrown from imported JavaScript functions. WASM_GLOBAL_DEFINE_INLINE_ACCESSORS(_swift_js_exception, i32, int32_t) #endif diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index 2b78b96b..ff42f3c7 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -1,5 +1,6 @@ import XCTest import JavaScriptKit +import JavaScriptEventLoop @_extern(wasm, module: "BridgeJSRuntimeTests", name: "runJsWorks") @_extern(c) @@ -49,8 +50,18 @@ struct TestError: Error { @JS func throwsWithSwiftHeapObjectResult() throws(JSException) -> Greeter { return Greeter(name: "Test") } @JS func throwsWithJSObjectResult() throws(JSException) -> JSObject { return JSObject() } +@JS func asyncRoundTripVoid() async -> Void { return } +@JS func asyncRoundTripInt(v: Int) async -> Int { return v } +@JS func asyncRoundTripFloat(v: Float) async -> Float { return v } +@JS func asyncRoundTripDouble(v: Double) async -> Double { return v } +@JS func asyncRoundTripBool(v: Bool) async -> Bool { return v } +@JS func asyncRoundTripString(v: String) async -> String { return v } +@JS func asyncRoundTripSwiftHeapObject(v: Greeter) async -> Greeter { return v } +@JS func asyncRoundTripJSObject(v: JSObject) async -> JSObject { return v } + @JS class Greeter { - var name: String + @JS var name: String + @JS let prefix: String = "Hello" nonisolated(unsafe) static var onDeinit: () -> Void = {} @@ -59,7 +70,7 @@ struct TestError: Error { } @JS func greet() -> String { - return "Hello, \(name)!" + return "\(prefix), \(name)!" } @JS func changeName(name: String) { self.name = name @@ -74,13 +85,382 @@ struct TestError: Error { g.changeName(name: name) } +// Test class without @JS init constructor +@JS class Calculator { + nonisolated(unsafe) static var onDeinit: () -> Void = {} + + @JS func square(value: Int) -> Int { + return value * value + } + + @JS func add(a: Int, b: Int) -> Int { + return a + b + } + + deinit { + Self.onDeinit() + } +} + +@JS internal class InternalGreeter {} +@JS public class PublicGreeter {} +@JS package class PackageGreeter {} + +@JS func createCalculator() -> Calculator { + return Calculator() +} + +@JS func useCalculator(calc: Calculator, x: Int, y: Int) -> Int { + return calc.add(a: calc.square(value: x), b: y) +} + +@JS func testGreeterToJSValue() -> JSObject { + let greeter = Greeter(name: "Test") + return greeter.jsValue.object! +} + +@JS func testCalculatorToJSValue() -> JSObject { + let calc = Calculator() + return calc.jsValue.object! +} + +@JS func testSwiftClassAsJSValue(greeter: Greeter) -> JSObject { + return greeter.jsValue.object! +} + +// MARK: - Enum Tests + +@JS enum Direction { + case north + case south + case east + case west +} + +@JS enum Status { + case loading + case success + case error +} + +@JS enum Theme: String { + case light = "light" + case dark = "dark" + case auto = "auto" +} + +@JS enum HttpStatus: Int { + case ok = 200 + case notFound = 404 + case serverError = 500 +} + +@JS(enumStyle: .tsEnum) enum TSDirection { + case north + case south + case east + case west +} + +@JS(enumStyle: .tsEnum) enum TSTheme: String { + case light = "light" + case dark = "dark" + case auto = "auto" +} + +@JS func setDirection(_ direction: Direction) -> Direction { + return direction +} + +@JS func getDirection() -> Direction { + return .north +} + +@JS func processDirection(_ input: Direction) -> Status { + switch input { + case .north, .south: return .success + case .east, .west: return .loading + } +} + +@JS func setTheme(_ theme: Theme) -> Theme { + return theme +} + +@JS func getTheme() -> Theme { + return .light +} + +@JS func setHttpStatus(_ status: HttpStatus) -> HttpStatus { + return status +} + +@JS func getHttpStatus() -> HttpStatus { + return .ok +} + +@JS func processTheme(_ theme: Theme) -> HttpStatus { + switch theme { + case .light: return .ok + case .dark: return .notFound + case .auto: return .serverError + } +} + +@JS func setTSDirection(_ direction: TSDirection) -> TSDirection { + return direction +} + +@JS func getTSDirection() -> TSDirection { + return .north +} + +@JS func setTSTheme(_ theme: TSTheme) -> TSTheme { + return theme +} + +@JS func getTSTheme() -> TSTheme { + return .light +} + +// MARK: - Namespace Enums + +@JS enum Utils { + @JS class Converter { + @JS init() {} + + @JS func toString(value: Int) -> String { + return String(value) + } + } +} + +@JS enum Networking { + @JS enum API { + @JS enum Method { + case get + case post + case put + case delete + } + @JS class HTTPServer { + @JS init() {} + @JS func call(_ method: Method) {} + } + } +} + +@JS enum Configuration { + @JS enum LogLevel: String { + case debug = "debug" + case info = "info" + case warning = "warning" + case error = "error" + } + + @JS enum Port: Int { + case http = 80 + case https = 443 + case development = 3000 + } +} + +@JS(namespace: "Networking.APIV2") +enum Internal { + @JS enum SupportedMethod { + case get + case post + } + @JS class TestServer { + @JS init() {} + @JS func call(_ method: SupportedMethod) {} + } +} + +@JS func echoNetworkingAPIMethod(_ method: Networking.API.Method) -> Networking.API.Method { + return method +} + +@JS func echoConfigurationLogLevel(_ level: Configuration.LogLevel) -> Configuration.LogLevel { + return level +} + +@JS func echoConfigurationPort(_ port: Configuration.Port) -> Configuration.Port { + return port +} + +@JS func processConfigurationLogLevel(_ level: Configuration.LogLevel) -> Configuration.Port { + switch level { + case .debug: return .development + case .info: return .http + case .warning: return .https + case .error: return .development + } +} + +@JS func echoInternalSupportedMethod(_ method: Internal.SupportedMethod) -> Internal.SupportedMethod { + return method +} + +// MARK: - Property Tests + +// Simple class for SwiftHeapObject property testing +@JS class SimplePropertyHolder { + @JS var value: Int + + @JS init(value: Int) { + self.value = value + } +} + +// Test class for various property types +@JS class PropertyHolder { + // Primitive properties + @JS var intValue: Int + @JS var floatValue: Float + @JS var doubleValue: Double + @JS var boolValue: Bool + @JS var stringValue: String + + // Readonly primitive properties + @JS let readonlyInt: Int = 42 + @JS let readonlyFloat: Float = 3.14 + @JS let readonlyDouble: Double = 2.718281828 + @JS let readonlyBool: Bool = true + @JS let readonlyString: String = "constant" + + // JSObject property + @JS var jsObject: JSObject + + // SwiftHeapObject property + @JS var sibling: SimplePropertyHolder + + // Lazy stored property + @JS lazy var lazyValue: String = "computed lazily" + + // Computed property with getter only (readonly) + @JS var computedReadonly: Int { + return intValue * 2 + } + + // Computed property with getter and setter + @JS var computedReadWrite: String { + get { + return "Value: \(intValue)" + } + set { + // Parse the number from "Value: X" format + if let range = newValue.range(of: "Value: "), + let number = Int(String(newValue[range.upperBound...])) + { + intValue = number + } + } + } + + // Property with property observers + @JS var observedProperty: Int { + willSet { + Self.willSetCallCount += 1 + Self.lastWillSetOldValue = self.observedProperty + Self.lastWillSetNewValue = newValue + } + didSet { + Self.didSetCallCount += 1 + Self.lastDidSetOldValue = oldValue + Self.lastDidSetNewValue = self.observedProperty + } + } + + // Static properties to track observer calls + nonisolated(unsafe) static var willSetCallCount: Int = 0 + nonisolated(unsafe) static var didSetCallCount: Int = 0 + nonisolated(unsafe) static var lastWillSetOldValue: Int = 0 + nonisolated(unsafe) static var lastWillSetNewValue: Int = 0 + nonisolated(unsafe) static var lastDidSetOldValue: Int = 0 + nonisolated(unsafe) static var lastDidSetNewValue: Int = 0 + + @JS init( + intValue: Int, + floatValue: Float, + doubleValue: Double, + boolValue: Bool, + stringValue: String, + jsObject: JSObject, + sibling: SimplePropertyHolder + ) { + self.intValue = intValue + self.floatValue = floatValue + self.doubleValue = doubleValue + self.boolValue = boolValue + self.stringValue = stringValue + self.jsObject = jsObject + self.sibling = sibling + self.observedProperty = intValue // Initialize observed property + } + + @JS func getAllValues() -> String { + return "int:\(intValue),float:\(floatValue),double:\(doubleValue),bool:\(boolValue),string:\(stringValue)" + } +} + +@JS func createPropertyHolder( + intValue: Int, + floatValue: Float, + doubleValue: Double, + boolValue: Bool, + stringValue: String, + jsObject: JSObject +) -> PropertyHolder { + let sibling = SimplePropertyHolder(value: 999) + return PropertyHolder( + intValue: intValue, + floatValue: floatValue, + doubleValue: doubleValue, + boolValue: boolValue, + stringValue: stringValue, + jsObject: jsObject, + sibling: sibling + ) +} + +@JS func testPropertyHolder(holder: PropertyHolder) -> String { + return holder.getAllValues() +} + +@JS func resetObserverCounts() { + PropertyHolder.willSetCallCount = 0 + PropertyHolder.didSetCallCount = 0 + PropertyHolder.lastWillSetOldValue = 0 + PropertyHolder.lastWillSetNewValue = 0 + PropertyHolder.lastDidSetOldValue = 0 + PropertyHolder.lastDidSetNewValue = 0 +} + +@JS func getObserverStats() -> String { + return + "willSet:\(PropertyHolder.willSetCallCount),didSet:\(PropertyHolder.didSetCallCount),willSetOld:\(PropertyHolder.lastWillSetOldValue),willSetNew:\(PropertyHolder.lastWillSetNewValue),didSetOld:\(PropertyHolder.lastDidSetOldValue),didSetNew:\(PropertyHolder.lastDidSetNewValue)" +} class ExportAPITests: XCTestCase { func testAll() { var hasDeinitGreeter = false + var hasDeinitCalculator = false + Greeter.onDeinit = { hasDeinitGreeter = true } + + Calculator.onDeinit = { + hasDeinitCalculator = true + } + runJsWorks() - XCTAssertTrue(hasDeinitGreeter) + + XCTAssertTrue(hasDeinitGreeter, "Greeter (with @JS init) should have been deinitialized") + XCTAssertTrue(hasDeinitCalculator, "Calculator (without @JS init) should have been deinitialized") + } + + func testAllAsync() async throws { + _ = try await runAsyncWorks().value() } } diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift index 2a91da9f..2c70c647 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift @@ -6,6 +6,227 @@ @_spi(BridgeJS) import JavaScriptKit +extension Direction { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Direction { + return Direction(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Direction { + return Direction(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .north + case 1: + self = .south + case 2: + self = .east + case 3: + self = .west + default: + return nil + } + } + + private var bridgeJSRawValue: Int32 { + switch self { + case .north: + return 0 + case .south: + return 1 + case .east: + return 2 + case .west: + return 3 + } + } +} + +extension Status { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Status { + return Status(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Status { + return Status(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .loading + case 1: + self = .success + case 2: + self = .error + default: + return nil + } + } + + private var bridgeJSRawValue: Int32 { + switch self { + case .loading: + return 0 + case .success: + return 1 + case .error: + return 2 + } + } +} + +extension Theme: _BridgedSwiftEnumNoPayload { +} + +extension HttpStatus: _BridgedSwiftEnumNoPayload { +} + +extension TSDirection { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> TSDirection { + return TSDirection(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> TSDirection { + return TSDirection(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .north + case 1: + self = .south + case 2: + self = .east + case 3: + self = .west + default: + return nil + } + } + + private var bridgeJSRawValue: Int32 { + switch self { + case .north: + return 0 + case .south: + return 1 + case .east: + return 2 + case .west: + return 3 + } + } +} + +extension TSTheme: _BridgedSwiftEnumNoPayload { +} + +extension Utils: _BridgedSwiftEnumNoPayload { +} + +extension Networking.API.Method { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Networking.API.Method { + return Networking.API.Method(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Networking.API.Method { + return Networking.API.Method(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .get + case 1: + self = .post + case 2: + self = .put + case 3: + self = .delete + default: + return nil + } + } + + private var bridgeJSRawValue: Int32 { + switch self { + case .get: + return 0 + case .post: + return 1 + case .put: + return 2 + case .delete: + return 3 + } + } +} + +extension Configuration.LogLevel: _BridgedSwiftEnumNoPayload { +} + +extension Configuration.Port: _BridgedSwiftEnumNoPayload { +} + +extension Internal.SupportedMethod { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Internal.SupportedMethod { + return Internal.SupportedMethod(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Internal.SupportedMethod { + return Internal.SupportedMethod(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .get + case 1: + self = .post + default: + return nil + } + } + + private var bridgeJSRawValue: Int32 { + switch self { + case .get: + return 0 + case .post: + return 1 + } + } +} + @_expose(wasm, "bjs_roundTripVoid") @_cdecl("bjs_roundTripVoid") public func _bjs_roundTripVoid() -> Void { @@ -20,8 +241,8 @@ public func _bjs_roundTripVoid() -> Void { @_cdecl("bjs_roundTripInt") public func _bjs_roundTripInt(v: Int32) -> Int32 { #if arch(wasm32) - let ret = roundTripInt(v: Int(v)) - return Int32(ret) + let ret = roundTripInt(v: Int.bridgeJSLiftParameter(v)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -31,8 +252,8 @@ public func _bjs_roundTripInt(v: Int32) -> Int32 { @_cdecl("bjs_roundTripFloat") public func _bjs_roundTripFloat(v: Float32) -> Float32 { #if arch(wasm32) - let ret = roundTripFloat(v: v) - return Float32(ret) + let ret = roundTripFloat(v: Float.bridgeJSLiftParameter(v)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -42,8 +263,8 @@ public func _bjs_roundTripFloat(v: Float32) -> Float32 { @_cdecl("bjs_roundTripDouble") public func _bjs_roundTripDouble(v: Float64) -> Float64 { #if arch(wasm32) - let ret = roundTripDouble(v: v) - return Float64(ret) + let ret = roundTripDouble(v: Double.bridgeJSLiftParameter(v)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -53,8 +274,8 @@ public func _bjs_roundTripDouble(v: Float64) -> Float64 { @_cdecl("bjs_roundTripBool") public func _bjs_roundTripBool(v: Int32) -> Int32 { #if arch(wasm32) - let ret = roundTripBool(v: v == 1) - return Int32(ret ? 1 : 0) + let ret = roundTripBool(v: Bool.bridgeJSLiftParameter(v)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -62,16 +283,10 @@ public func _bjs_roundTripBool(v: Int32) -> Int32 { @_expose(wasm, "bjs_roundTripString") @_cdecl("bjs_roundTripString") -public func _bjs_roundTripString(vBytes: Int32, vLen: Int32) -> Void { +public func _bjs_roundTripString(vBytes: Int32, vLength: Int32) -> Void { #if arch(wasm32) - let v = String(unsafeUninitializedCapacity: Int(vLen)) { b in - _swift_js_init_memory(vBytes, b.baseAddress.unsafelyUnwrapped) - return Int(vLen) - } - var ret = roundTripString(v: v) - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = roundTripString(v: String.bridgeJSLiftParameter(vBytes, vLength)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -81,8 +296,8 @@ public func _bjs_roundTripString(vBytes: Int32, vLen: Int32) -> Void { @_cdecl("bjs_roundTripSwiftHeapObject") public func _bjs_roundTripSwiftHeapObject(v: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { #if arch(wasm32) - let ret = roundTripSwiftHeapObject(v: Unmanaged.fromOpaque(v).takeUnretainedValue()) - return Unmanaged.passRetained(ret).toOpaque() + let ret = roundTripSwiftHeapObject(v: Greeter.bridgeJSLiftParameter(v)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -92,8 +307,8 @@ public func _bjs_roundTripSwiftHeapObject(v: UnsafeMutableRawPointer) -> UnsafeM @_cdecl("bjs_roundTripJSObject") public func _bjs_roundTripJSObject(v: Int32) -> Int32 { #if arch(wasm32) - let ret = roundTripJSObject(v: JSObject(id: UInt32(bitPattern: v))) - return _swift_js_retain(Int32(bitPattern: ret.id)) + let ret = roundTripJSObject(v: JSObject.bridgeJSLiftParameter(v)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -104,7 +319,7 @@ public func _bjs_roundTripJSObject(v: Int32) -> Int32 { public func _bjs_throwsSwiftError(shouldThrow: Int32) -> Void { #if arch(wasm32) do { - try throwsSwiftError(shouldThrow: shouldThrow == 1) + try throwsSwiftError(shouldThrow: Bool.bridgeJSLiftParameter(shouldThrow)) } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -129,7 +344,7 @@ public func _bjs_throwsWithIntResult() -> Int32 { #if arch(wasm32) do { let ret = try throwsWithIntResult() - return Int32(ret) + return ret.bridgeJSLowerReturn() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -153,10 +368,8 @@ public func _bjs_throwsWithIntResult() -> Int32 { public func _bjs_throwsWithStringResult() -> Void { #if arch(wasm32) do { - var ret = try throwsWithStringResult() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = try throwsWithStringResult() + return ret.bridgeJSLowerReturn() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -181,7 +394,7 @@ public func _bjs_throwsWithBoolResult() -> Int32 { #if arch(wasm32) do { let ret = try throwsWithBoolResult() - return Int32(ret ? 1 : 0) + return ret.bridgeJSLowerReturn() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -206,7 +419,7 @@ public func _bjs_throwsWithFloatResult() -> Float32 { #if arch(wasm32) do { let ret = try throwsWithFloatResult() - return Float32(ret) + return ret.bridgeJSLowerReturn() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -231,7 +444,7 @@ public func _bjs_throwsWithDoubleResult() -> Float64 { #if arch(wasm32) do { let ret = try throwsWithDoubleResult() - return Float64(ret) + return ret.bridgeJSLowerReturn() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -256,7 +469,7 @@ public func _bjs_throwsWithSwiftHeapObjectResult() -> UnsafeMutableRawPointer { #if arch(wasm32) do { let ret = try throwsWithSwiftHeapObjectResult() - return Unmanaged.passRetained(ret).toOpaque() + return ret.bridgeJSLowerReturn() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -281,7 +494,7 @@ public func _bjs_throwsWithJSObjectResult() -> Int32 { #if arch(wasm32) do { let ret = try throwsWithJSObjectResult() - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -300,64 +513,1081 @@ public func _bjs_throwsWithJSObjectResult() -> Int32 { #endif } +@_expose(wasm, "bjs_asyncRoundTripVoid") +@_cdecl("bjs_asyncRoundTripVoid") +public func _bjs_asyncRoundTripVoid() -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + await asyncRoundTripVoid() + } .jsObject + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripInt") +@_cdecl("bjs_asyncRoundTripInt") +public func _bjs_asyncRoundTripInt(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripInt(v: Int.bridgeJSLiftParameter(v)).jsValue + } .jsObject + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripFloat") +@_cdecl("bjs_asyncRoundTripFloat") +public func _bjs_asyncRoundTripFloat(v: Float32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripFloat(v: Float.bridgeJSLiftParameter(v)).jsValue + } .jsObject + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripDouble") +@_cdecl("bjs_asyncRoundTripDouble") +public func _bjs_asyncRoundTripDouble(v: Float64) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripDouble(v: Double.bridgeJSLiftParameter(v)).jsValue + } .jsObject + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripBool") +@_cdecl("bjs_asyncRoundTripBool") +public func _bjs_asyncRoundTripBool(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripBool(v: Bool.bridgeJSLiftParameter(v)).jsValue + } .jsObject + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripString") +@_cdecl("bjs_asyncRoundTripString") +public func _bjs_asyncRoundTripString(vBytes: Int32, vLength: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripString(v: String.bridgeJSLiftParameter(vBytes, vLength)).jsValue + } .jsObject + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripSwiftHeapObject") +@_cdecl("bjs_asyncRoundTripSwiftHeapObject") +public func _bjs_asyncRoundTripSwiftHeapObject(v: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripSwiftHeapObject(v: Greeter.bridgeJSLiftParameter(v)).jsValue + } .jsObject + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripJSObject") +@_cdecl("bjs_asyncRoundTripJSObject") +public func _bjs_asyncRoundTripJSObject(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripJSObject(v: JSObject.bridgeJSLiftParameter(v)).jsValue + } .jsObject + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_takeGreeter") @_cdecl("bjs_takeGreeter") -public func _bjs_takeGreeter(g: UnsafeMutableRawPointer, nameBytes: Int32, nameLen: Int32) -> Void { +public func _bjs_takeGreeter(g: UnsafeMutableRawPointer, nameBytes: Int32, nameLength: Int32) -> Void { #if arch(wasm32) - let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in - _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) - return Int(nameLen) - } - takeGreeter(g: Unmanaged.fromOpaque(g).takeUnretainedValue(), name: name) + takeGreeter(g: Greeter.bridgeJSLiftParameter(g), name: String.bridgeJSLiftParameter(nameBytes, nameLength)) #else fatalError("Only available on WebAssembly") #endif } -@_expose(wasm, "bjs_Greeter_init") -@_cdecl("bjs_Greeter_init") -public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutableRawPointer { +@_expose(wasm, "bjs_createCalculator") +@_cdecl("bjs_createCalculator") +public func _bjs_createCalculator() -> UnsafeMutableRawPointer { #if arch(wasm32) - let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in - _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) - return Int(nameLen) - } - let ret = Greeter(name: name) - return Unmanaged.passRetained(ret).toOpaque() + let ret = createCalculator() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif } -@_expose(wasm, "bjs_Greeter_greet") -@_cdecl("bjs_Greeter_greet") -public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { +@_expose(wasm, "bjs_useCalculator") +@_cdecl("bjs_useCalculator") +public func _bjs_useCalculator(calc: UnsafeMutableRawPointer, x: Int32, y: Int32) -> Int32 { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().greet() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = useCalculator(calc: Calculator.bridgeJSLiftParameter(calc), x: Int.bridgeJSLiftParameter(x), y: Int.bridgeJSLiftParameter(y)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif } -@_expose(wasm, "bjs_Greeter_changeName") -@_cdecl("bjs_Greeter_changeName") -public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: Int32, nameLen: Int32) -> Void { +@_expose(wasm, "bjs_testGreeterToJSValue") +@_cdecl("bjs_testGreeterToJSValue") +public func _bjs_testGreeterToJSValue() -> Int32 { #if arch(wasm32) - let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in - _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) - return Int(nameLen) - } - Unmanaged.fromOpaque(_self).takeUnretainedValue().changeName(name: name) + let ret = testGreeterToJSValue() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif } -@_expose(wasm, "bjs_Greeter_deinit") -@_cdecl("bjs_Greeter_deinit") -public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { - Unmanaged.fromOpaque(pointer).release() +@_expose(wasm, "bjs_testCalculatorToJSValue") +@_cdecl("bjs_testCalculatorToJSValue") +public func _bjs_testCalculatorToJSValue() -> Int32 { + #if arch(wasm32) + let ret = testCalculatorToJSValue() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_testSwiftClassAsJSValue") +@_cdecl("bjs_testSwiftClassAsJSValue") +public func _bjs_testSwiftClassAsJSValue(greeter: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = testSwiftClassAsJSValue(greeter: Greeter.bridgeJSLiftParameter(greeter)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setDirection") +@_cdecl("bjs_setDirection") +public func _bjs_setDirection(direction: Int32) -> Int32 { + #if arch(wasm32) + let ret = setDirection(_: Direction.bridgeJSLiftParameter(direction)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getDirection") +@_cdecl("bjs_getDirection") +public func _bjs_getDirection() -> Int32 { + #if arch(wasm32) + let ret = getDirection() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_processDirection") +@_cdecl("bjs_processDirection") +public func _bjs_processDirection(input: Int32) -> Int32 { + #if arch(wasm32) + let ret = processDirection(_: Direction.bridgeJSLiftParameter(input)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setTheme") +@_cdecl("bjs_setTheme") +public func _bjs_setTheme(themeBytes: Int32, themeLength: Int32) -> Void { + #if arch(wasm32) + let ret = setTheme(_: Theme.bridgeJSLiftParameter(themeBytes, themeLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getTheme") +@_cdecl("bjs_getTheme") +public func _bjs_getTheme() -> Void { + #if arch(wasm32) + let ret = getTheme() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setHttpStatus") +@_cdecl("bjs_setHttpStatus") +public func _bjs_setHttpStatus(status: Int32) -> Int32 { + #if arch(wasm32) + let ret = setHttpStatus(_: HttpStatus.bridgeJSLiftParameter(status)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getHttpStatus") +@_cdecl("bjs_getHttpStatus") +public func _bjs_getHttpStatus() -> Int32 { + #if arch(wasm32) + let ret = getHttpStatus() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_processTheme") +@_cdecl("bjs_processTheme") +public func _bjs_processTheme(themeBytes: Int32, themeLength: Int32) -> Int32 { + #if arch(wasm32) + let ret = processTheme(_: Theme.bridgeJSLiftParameter(themeBytes, themeLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setTSDirection") +@_cdecl("bjs_setTSDirection") +public func _bjs_setTSDirection(direction: Int32) -> Int32 { + #if arch(wasm32) + let ret = setTSDirection(_: TSDirection.bridgeJSLiftParameter(direction)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getTSDirection") +@_cdecl("bjs_getTSDirection") +public func _bjs_getTSDirection() -> Int32 { + #if arch(wasm32) + let ret = getTSDirection() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setTSTheme") +@_cdecl("bjs_setTSTheme") +public func _bjs_setTSTheme(themeBytes: Int32, themeLength: Int32) -> Void { + #if arch(wasm32) + let ret = setTSTheme(_: TSTheme.bridgeJSLiftParameter(themeBytes, themeLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getTSTheme") +@_cdecl("bjs_getTSTheme") +public func _bjs_getTSTheme() -> Void { + #if arch(wasm32) + let ret = getTSTheme() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_echoNetworkingAPIMethod") +@_cdecl("bjs_echoNetworkingAPIMethod") +public func _bjs_echoNetworkingAPIMethod(method: Int32) -> Int32 { + #if arch(wasm32) + let ret = echoNetworkingAPIMethod(_: Networking.API.Method.bridgeJSLiftParameter(method)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_echoConfigurationLogLevel") +@_cdecl("bjs_echoConfigurationLogLevel") +public func _bjs_echoConfigurationLogLevel(levelBytes: Int32, levelLength: Int32) -> Void { + #if arch(wasm32) + let ret = echoConfigurationLogLevel(_: Configuration.LogLevel.bridgeJSLiftParameter(levelBytes, levelLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_echoConfigurationPort") +@_cdecl("bjs_echoConfigurationPort") +public func _bjs_echoConfigurationPort(port: Int32) -> Int32 { + #if arch(wasm32) + let ret = echoConfigurationPort(_: Configuration.Port.bridgeJSLiftParameter(port)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_processConfigurationLogLevel") +@_cdecl("bjs_processConfigurationLogLevel") +public func _bjs_processConfigurationLogLevel(levelBytes: Int32, levelLength: Int32) -> Int32 { + #if arch(wasm32) + let ret = processConfigurationLogLevel(_: Configuration.LogLevel.bridgeJSLiftParameter(levelBytes, levelLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_echoInternalSupportedMethod") +@_cdecl("bjs_echoInternalSupportedMethod") +public func _bjs_echoInternalSupportedMethod(method: Int32) -> Int32 { + #if arch(wasm32) + let ret = echoInternalSupportedMethod(_: Internal.SupportedMethod.bridgeJSLiftParameter(method)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_createPropertyHolder") +@_cdecl("bjs_createPropertyHolder") +public func _bjs_createPropertyHolder(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLength: Int32, jsObject: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = createPropertyHolder(intValue: Int.bridgeJSLiftParameter(intValue), floatValue: Float.bridgeJSLiftParameter(floatValue), doubleValue: Double.bridgeJSLiftParameter(doubleValue), boolValue: Bool.bridgeJSLiftParameter(boolValue), stringValue: String.bridgeJSLiftParameter(stringValueBytes, stringValueLength), jsObject: JSObject.bridgeJSLiftParameter(jsObject)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_testPropertyHolder") +@_cdecl("bjs_testPropertyHolder") +public func _bjs_testPropertyHolder(holder: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = testPropertyHolder(holder: PropertyHolder.bridgeJSLiftParameter(holder)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_resetObserverCounts") +@_cdecl("bjs_resetObserverCounts") +public func _bjs_resetObserverCounts() -> Void { + #if arch(wasm32) + resetObserverCounts() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getObserverStats") +@_cdecl("bjs_getObserverStats") +public func _bjs_getObserverStats() -> Void { + #if arch(wasm32) + let ret = getObserverStats() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_init") +@_cdecl("bjs_Greeter_init") +public func _bjs_Greeter_init(nameBytes: Int32, nameLength: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Greeter(name: String.bridgeJSLiftParameter(nameBytes, nameLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_greet") +@_cdecl("bjs_Greeter_greet") +public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = Greeter.bridgeJSLiftParameter(_self).greet() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_changeName") +@_cdecl("bjs_Greeter_changeName") +public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: Int32, nameLength: Int32) -> Void { + #if arch(wasm32) + Greeter.bridgeJSLiftParameter(_self).changeName(name: String.bridgeJSLiftParameter(nameBytes, nameLength)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_name_get") +@_cdecl("bjs_Greeter_name_get") +public func _bjs_Greeter_name_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = Greeter.bridgeJSLiftParameter(_self).name + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_name_set") +@_cdecl("bjs_Greeter_name_set") +public func _bjs_Greeter_name_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + Greeter.bridgeJSLiftParameter(_self).name = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_prefix_get") +@_cdecl("bjs_Greeter_prefix_get") +public func _bjs_Greeter_prefix_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = Greeter.bridgeJSLiftParameter(_self).prefix + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_deinit") +@_cdecl("bjs_Greeter_deinit") +public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Greeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_Greeter_wrap") + func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_Calculator_square") +@_cdecl("bjs_Calculator_square") +public func _bjs_Calculator_square(_self: UnsafeMutableRawPointer, value: Int32) -> Int32 { + #if arch(wasm32) + let ret = Calculator.bridgeJSLiftParameter(_self).square(value: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Calculator_add") +@_cdecl("bjs_Calculator_add") +public func _bjs_Calculator_add(_self: UnsafeMutableRawPointer, a: Int32, b: Int32) -> Int32 { + #if arch(wasm32) + let ret = Calculator.bridgeJSLiftParameter(_self).add(a: Int.bridgeJSLiftParameter(a), b: Int.bridgeJSLiftParameter(b)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Calculator_deinit") +@_cdecl("bjs_Calculator_deinit") +public func _bjs_Calculator_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Calculator: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_Calculator_wrap") + func _bjs_Calculator_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_Calculator_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_Calculator_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_InternalGreeter_deinit") +@_cdecl("bjs_InternalGreeter_deinit") +public func _bjs_InternalGreeter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension InternalGreeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + internal var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_InternalGreeter_wrap") + func _bjs_InternalGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_InternalGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_InternalGreeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_PublicGreeter_deinit") +@_cdecl("bjs_PublicGreeter_deinit") +public func _bjs_PublicGreeter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension PublicGreeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + public var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_PublicGreeter_wrap") + func _bjs_PublicGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_PublicGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_PublicGreeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_PackageGreeter_deinit") +@_cdecl("bjs_PackageGreeter_deinit") +public func _bjs_PackageGreeter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension PackageGreeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + package var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_PackageGreeter_wrap") + func _bjs_PackageGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_PackageGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_PackageGreeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_Converter_init") +@_cdecl("bjs_Converter_init") +public func _bjs_Converter_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Utils.Converter() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Converter_toString") +@_cdecl("bjs_Converter_toString") +public func _bjs_Converter_toString(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + let ret = Utils.Converter.bridgeJSLiftParameter(_self).toString(value: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Converter_deinit") +@_cdecl("bjs_Converter_deinit") +public func _bjs_Converter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Utils.Converter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_Converter_wrap") + func _bjs_Converter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_Converter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_Converter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_HTTPServer_init") +@_cdecl("bjs_HTTPServer_init") +public func _bjs_HTTPServer_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Networking.API.HTTPServer() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_HTTPServer_call") +@_cdecl("bjs_HTTPServer_call") +public func _bjs_HTTPServer_call(_self: UnsafeMutableRawPointer, method: Int32) -> Void { + #if arch(wasm32) + Networking.API.HTTPServer.bridgeJSLiftParameter(_self).call(_: Networking.API.Method.bridgeJSLiftParameter(method)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_HTTPServer_deinit") +@_cdecl("bjs_HTTPServer_deinit") +public func _bjs_HTTPServer_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Networking.API.HTTPServer: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_HTTPServer_wrap") + func _bjs_HTTPServer_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_HTTPServer_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_HTTPServer_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_TestServer_init") +@_cdecl("bjs_TestServer_init") +public func _bjs_TestServer_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Internal.TestServer() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_TestServer_call") +@_cdecl("bjs_TestServer_call") +public func _bjs_TestServer_call(_self: UnsafeMutableRawPointer, method: Int32) -> Void { + #if arch(wasm32) + Internal.TestServer.bridgeJSLiftParameter(_self).call(_: Internal.SupportedMethod.bridgeJSLiftParameter(method)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_TestServer_deinit") +@_cdecl("bjs_TestServer_deinit") +public func _bjs_TestServer_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Internal.TestServer: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_TestServer_wrap") + func _bjs_TestServer_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_TestServer_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_TestServer_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_SimplePropertyHolder_init") +@_cdecl("bjs_SimplePropertyHolder_init") +public func _bjs_SimplePropertyHolder_init(value: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = SimplePropertyHolder(value: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimplePropertyHolder_value_get") +@_cdecl("bjs_SimplePropertyHolder_value_get") +public func _bjs_SimplePropertyHolder_value_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = SimplePropertyHolder.bridgeJSLiftParameter(_self).value + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimplePropertyHolder_value_set") +@_cdecl("bjs_SimplePropertyHolder_value_set") +public func _bjs_SimplePropertyHolder_value_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + SimplePropertyHolder.bridgeJSLiftParameter(_self).value = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimplePropertyHolder_deinit") +@_cdecl("bjs_SimplePropertyHolder_deinit") +public func _bjs_SimplePropertyHolder_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension SimplePropertyHolder: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_SimplePropertyHolder_wrap") + func _bjs_SimplePropertyHolder_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_SimplePropertyHolder_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_SimplePropertyHolder_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_PropertyHolder_init") +@_cdecl("bjs_PropertyHolder_init") +public func _bjs_PropertyHolder_init(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLength: Int32, jsObject: Int32, sibling: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = PropertyHolder(intValue: Int.bridgeJSLiftParameter(intValue), floatValue: Float.bridgeJSLiftParameter(floatValue), doubleValue: Double.bridgeJSLiftParameter(doubleValue), boolValue: Bool.bridgeJSLiftParameter(boolValue), stringValue: String.bridgeJSLiftParameter(stringValueBytes, stringValueLength), jsObject: JSObject.bridgeJSLiftParameter(jsObject), sibling: SimplePropertyHolder.bridgeJSLiftParameter(sibling)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_getAllValues") +@_cdecl("bjs_PropertyHolder_getAllValues") +public func _bjs_PropertyHolder_getAllValues(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).getAllValues() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_intValue_get") +@_cdecl("bjs_PropertyHolder_intValue_get") +public func _bjs_PropertyHolder_intValue_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).intValue + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_intValue_set") +@_cdecl("bjs_PropertyHolder_intValue_set") +public func _bjs_PropertyHolder_intValue_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).intValue = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_floatValue_get") +@_cdecl("bjs_PropertyHolder_floatValue_get") +public func _bjs_PropertyHolder_floatValue_get(_self: UnsafeMutableRawPointer) -> Float32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).floatValue + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_floatValue_set") +@_cdecl("bjs_PropertyHolder_floatValue_set") +public func _bjs_PropertyHolder_floatValue_set(_self: UnsafeMutableRawPointer, value: Float32) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).floatValue = Float.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_doubleValue_get") +@_cdecl("bjs_PropertyHolder_doubleValue_get") +public func _bjs_PropertyHolder_doubleValue_get(_self: UnsafeMutableRawPointer) -> Float64 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).doubleValue + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_doubleValue_set") +@_cdecl("bjs_PropertyHolder_doubleValue_set") +public func _bjs_PropertyHolder_doubleValue_set(_self: UnsafeMutableRawPointer, value: Float64) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).doubleValue = Double.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_boolValue_get") +@_cdecl("bjs_PropertyHolder_boolValue_get") +public func _bjs_PropertyHolder_boolValue_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).boolValue + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_boolValue_set") +@_cdecl("bjs_PropertyHolder_boolValue_set") +public func _bjs_PropertyHolder_boolValue_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).boolValue = Bool.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_stringValue_get") +@_cdecl("bjs_PropertyHolder_stringValue_get") +public func _bjs_PropertyHolder_stringValue_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).stringValue + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_stringValue_set") +@_cdecl("bjs_PropertyHolder_stringValue_set") +public func _bjs_PropertyHolder_stringValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).stringValue = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyInt_get") +@_cdecl("bjs_PropertyHolder_readonlyInt_get") +public func _bjs_PropertyHolder_readonlyInt_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyInt + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyFloat_get") +@_cdecl("bjs_PropertyHolder_readonlyFloat_get") +public func _bjs_PropertyHolder_readonlyFloat_get(_self: UnsafeMutableRawPointer) -> Float32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyFloat + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyDouble_get") +@_cdecl("bjs_PropertyHolder_readonlyDouble_get") +public func _bjs_PropertyHolder_readonlyDouble_get(_self: UnsafeMutableRawPointer) -> Float64 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyDouble + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyBool_get") +@_cdecl("bjs_PropertyHolder_readonlyBool_get") +public func _bjs_PropertyHolder_readonlyBool_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyBool + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyString_get") +@_cdecl("bjs_PropertyHolder_readonlyString_get") +public func _bjs_PropertyHolder_readonlyString_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyString + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_jsObject_get") +@_cdecl("bjs_PropertyHolder_jsObject_get") +public func _bjs_PropertyHolder_jsObject_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).jsObject + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_jsObject_set") +@_cdecl("bjs_PropertyHolder_jsObject_set") +public func _bjs_PropertyHolder_jsObject_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).jsObject = JSObject.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_sibling_get") +@_cdecl("bjs_PropertyHolder_sibling_get") +public func _bjs_PropertyHolder_sibling_get(_self: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).sibling + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_sibling_set") +@_cdecl("bjs_PropertyHolder_sibling_set") +public func _bjs_PropertyHolder_sibling_set(_self: UnsafeMutableRawPointer, value: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).sibling = SimplePropertyHolder.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_lazyValue_get") +@_cdecl("bjs_PropertyHolder_lazyValue_get") +public func _bjs_PropertyHolder_lazyValue_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).lazyValue + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_lazyValue_set") +@_cdecl("bjs_PropertyHolder_lazyValue_set") +public func _bjs_PropertyHolder_lazyValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).lazyValue = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_computedReadonly_get") +@_cdecl("bjs_PropertyHolder_computedReadonly_get") +public func _bjs_PropertyHolder_computedReadonly_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).computedReadonly + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_computedReadWrite_get") +@_cdecl("bjs_PropertyHolder_computedReadWrite_get") +public func _bjs_PropertyHolder_computedReadWrite_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).computedReadWrite + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_computedReadWrite_set") +@_cdecl("bjs_PropertyHolder_computedReadWrite_set") +public func _bjs_PropertyHolder_computedReadWrite_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).computedReadWrite = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_observedProperty_get") +@_cdecl("bjs_PropertyHolder_observedProperty_get") +public func _bjs_PropertyHolder_observedProperty_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).observedProperty + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_observedProperty_set") +@_cdecl("bjs_PropertyHolder_observedProperty_set") +public func _bjs_PropertyHolder_observedProperty_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + PropertyHolder.bridgeJSLiftParameter(_self).observedProperty = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_deinit") +@_cdecl("bjs_PropertyHolder_deinit") +public func _bjs_PropertyHolder_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension PropertyHolder: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_PropertyHolder_wrap") + func _bjs_PropertyHolder_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_PropertyHolder_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_PropertyHolder_wrap(Unmanaged.passRetained(self).toOpaque())))) + } } \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift index fd558ab8..19bc82fc 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift @@ -30,11 +30,11 @@ func jsRoundTripNumber(_ v: Double) throws(JSException) -> Double { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_jsRoundTripNumber(v) + let ret = bjs_jsRoundTripNumber(v.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return Double(ret) + return Double.bridgeJSLiftReturn(ret) } func jsRoundTripBool(_ v: Bool) throws(JSException) -> Bool { @@ -46,11 +46,11 @@ func jsRoundTripBool(_ v: Bool) throws(JSException) -> Bool { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_jsRoundTripBool(Int32(v ? 1 : 0)) + let ret = bjs_jsRoundTripBool(v.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return ret == 1 + return Bool.bridgeJSLiftReturn(ret) } func jsRoundTripString(_ v: String) throws(JSException) -> String { @@ -62,18 +62,11 @@ func jsRoundTripString(_ v: String) throws(JSException) -> String { fatalError("Only available on WebAssembly") } #endif - var v = v - let vId = v.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - let ret = bjs_jsRoundTripString(vId) + let ret = bjs_jsRoundTripString(v.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } func jsThrowOrVoid(_ shouldThrow: Bool) throws(JSException) -> Void { @@ -85,7 +78,7 @@ func jsThrowOrVoid(_ shouldThrow: Bool) throws(JSException) -> Void { fatalError("Only available on WebAssembly") } #endif - bjs_jsThrowOrVoid(Int32(shouldThrow ? 1 : 0)) + bjs_jsThrowOrVoid(shouldThrow.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -100,11 +93,11 @@ func jsThrowOrNumber(_ shouldThrow: Bool) throws(JSException) -> Double { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_jsThrowOrNumber(Int32(shouldThrow ? 1 : 0)) + let ret = bjs_jsThrowOrNumber(shouldThrow.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return Double(ret) + return Double.bridgeJSLiftReturn(ret) } func jsThrowOrBool(_ shouldThrow: Bool) throws(JSException) -> Bool { @@ -116,11 +109,11 @@ func jsThrowOrBool(_ shouldThrow: Bool) throws(JSException) -> Bool { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_jsThrowOrBool(Int32(shouldThrow ? 1 : 0)) + let ret = bjs_jsThrowOrBool(shouldThrow.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return ret == 1 + return Bool.bridgeJSLiftReturn(ret) } func jsThrowOrString(_ shouldThrow: Bool) throws(JSException) -> String { @@ -132,25 +125,34 @@ func jsThrowOrString(_ shouldThrow: Bool) throws(JSException) -> String { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_jsThrowOrString(Int32(shouldThrow ? 1 : 0)) + let ret = bjs_jsThrowOrString(shouldThrow.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } -struct JsGreeter { - let this: JSObject - - init(this: JSObject) { - self.this = this +func runAsyncWorks() throws(JSException) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_runAsyncWorks") + func bjs_runAsyncWorks() -> Int32 + #else + func bjs_runAsyncWorks() -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_runAsyncWorks() + if let error = _swift_js_take_exception() { + throw error } + return JSPromise.bridgeJSLiftReturn(ret) +} + +struct JsGreeter: _JSBridgedClass { + let jsObject: JSObject - init(takingThis this: Int32) { - self.this = JSObject(id: UInt32(bitPattern: this)) + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject } init(_ name: String, _ prefix: String) throws(JSException) { @@ -162,19 +164,11 @@ struct JsGreeter { fatalError("Only available on WebAssembly") } #endif - var name = name - let nameId = name.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - var prefix = prefix - let prefixId = prefix.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - let ret = bjs_JsGreeter_init(nameId, prefixId) + let ret = bjs_JsGreeter_init(name.bridgeJSLowerParameter(), prefix.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - self.this = JSObject(id: UInt32(bitPattern: ret)) + self.jsObject = JSObject(id: UInt32(bitPattern: ret)) } var name: String { @@ -187,14 +181,11 @@ struct JsGreeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_JsGreeter_name_get(Int32(bitPattern: self.this.id)) + let ret = bjs_JsGreeter_name_get(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } } @@ -207,11 +198,7 @@ struct JsGreeter { fatalError("Only available on WebAssembly") } #endif - var newValue = newValue - let newValueId = newValue.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_JsGreeter_name_set(Int32(bitPattern: self.this.id), newValueId) + bjs_JsGreeter_name_set(self.bridgeJSLowerParameter(), newValue.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -227,14 +214,11 @@ struct JsGreeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_JsGreeter_prefix_get(Int32(bitPattern: self.this.id)) + let ret = bjs_JsGreeter_prefix_get(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } } @@ -247,14 +231,11 @@ struct JsGreeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_JsGreeter_greet(Int32(bitPattern: self.this.id)) + let ret = bjs_JsGreeter_greet(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } func changeName(_ name: String) throws(JSException) -> Void { @@ -266,11 +247,7 @@ struct JsGreeter { fatalError("Only available on WebAssembly") } #endif - var name = name - let nameId = name.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_JsGreeter_changeName(Int32(bitPattern: self.this.id), nameId) + bjs_JsGreeter_changeName(self.bridgeJSLowerParameter(), name.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json index 7a467cc3..8e65a50d 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -61,362 +61,2022 @@ } } ], - "name" : "Greeter" + "name" : "Greeter", + "properties" : [ + { + "isReadonly" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "prefix", + "type" : { + "string" : { + + } + } + } + ], + "swiftCallName" : "Greeter" + }, + { + "methods" : [ + { + "abiName" : "bjs_Calculator_square", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "square", + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "int" : { + + } + } + }, + { + "abiName" : "bjs_Calculator_add", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "add", + "parameters" : [ + { + "label" : "a", + "name" : "a", + "type" : { + "int" : { + + } + } + }, + { + "label" : "b", + "name" : "b", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "int" : { + + } + } + } + ], + "name" : "Calculator", + "properties" : [ + + ], + "swiftCallName" : "Calculator" + }, + { + "explicitAccessControl" : "internal", + "methods" : [ + + ], + "name" : "InternalGreeter", + "properties" : [ + + ], + "swiftCallName" : "InternalGreeter" + }, + { + "explicitAccessControl" : "public", + "methods" : [ + + ], + "name" : "PublicGreeter", + "properties" : [ + + ], + "swiftCallName" : "PublicGreeter" + }, + { + "explicitAccessControl" : "package", + "methods" : [ + + ], + "name" : "PackageGreeter", + "properties" : [ + + ], + "swiftCallName" : "PackageGreeter" + }, + { + "constructor" : { + "abiName" : "bjs_Converter_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_Converter_toString", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "toString", + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + } + ], + "name" : "Converter", + "namespace" : [ + "Utils" + ], + "properties" : [ + + ], + "swiftCallName" : "Utils.Converter" + }, + { + "constructor" : { + "abiName" : "bjs_HTTPServer_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_HTTPServer_call", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "call", + "parameters" : [ + { + "label" : "_", + "name" : "method", + "type" : { + "caseEnum" : { + "_0" : "Networking.API.Method" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + } + ], + "name" : "HTTPServer", + "namespace" : [ + "Networking", + "API" + ], + "properties" : [ + + ], + "swiftCallName" : "Networking.API.HTTPServer" + }, + { + "constructor" : { + "abiName" : "bjs_TestServer_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_TestServer_call", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "call", + "parameters" : [ + { + "label" : "_", + "name" : "method", + "type" : { + "caseEnum" : { + "_0" : "Internal.SupportedMethod" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + } + ], + "name" : "TestServer", + "namespace" : [ + "Networking", + "APIV2", + "Internal" + ], + "properties" : [ + + ], + "swiftCallName" : "Internal.TestServer" + }, + { + "constructor" : { + "abiName" : "bjs_SimplePropertyHolder_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "int" : { + + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "SimplePropertyHolder", + "properties" : [ + { + "isReadonly" : false, + "name" : "value", + "type" : { + "int" : { + + } + } + } + ], + "swiftCallName" : "SimplePropertyHolder" + }, + { + "constructor" : { + "abiName" : "bjs_PropertyHolder_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "intValue", + "name" : "intValue", + "type" : { + "int" : { + + } + } + }, + { + "label" : "floatValue", + "name" : "floatValue", + "type" : { + "float" : { + + } + } + }, + { + "label" : "doubleValue", + "name" : "doubleValue", + "type" : { + "double" : { + + } + } + }, + { + "label" : "boolValue", + "name" : "boolValue", + "type" : { + "bool" : { + + } + } + }, + { + "label" : "stringValue", + "name" : "stringValue", + "type" : { + "string" : { + + } + } + }, + { + "label" : "jsObject", + "name" : "jsObject", + "type" : { + "jsObject" : { + + } + } + }, + { + "label" : "sibling", + "name" : "sibling", + "type" : { + "swiftHeapObject" : { + "_0" : "SimplePropertyHolder" + } + } + } + ] + }, + "methods" : [ + { + "abiName" : "bjs_PropertyHolder_getAllValues", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getAllValues", + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + } + } + ], + "name" : "PropertyHolder", + "properties" : [ + { + "isReadonly" : false, + "name" : "intValue", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "floatValue", + "type" : { + "float" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "doubleValue", + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "boolValue", + "type" : { + "bool" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "stringValue", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyInt", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyFloat", + "type" : { + "float" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyDouble", + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyBool", + "type" : { + "bool" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyString", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "jsObject", + "type" : { + "jsObject" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "sibling", + "type" : { + "swiftHeapObject" : { + "_0" : "SimplePropertyHolder" + } + } + }, + { + "isReadonly" : false, + "name" : "lazyValue", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "computedReadonly", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "computedReadWrite", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "observedProperty", + "type" : { + "int" : { + + } + } + } + ], + "swiftCallName" : "PropertyHolder" + } + ], + "enums" : [ + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "north" + }, + { + "associatedValues" : [ + + ], + "name" : "south" + }, + { + "associatedValues" : [ + + ], + "name" : "east" + }, + { + "associatedValues" : [ + + ], + "name" : "west" + } + ], + "emitStyle" : "const", + "name" : "Direction", + "swiftCallName" : "Direction" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "loading" + }, + { + "associatedValues" : [ + + ], + "name" : "success" + }, + { + "associatedValues" : [ + + ], + "name" : "error" + } + ], + "emitStyle" : "const", + "name" : "Status", + "swiftCallName" : "Status" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "light", + "rawValue" : "light" + }, + { + "associatedValues" : [ + + ], + "name" : "dark", + "rawValue" : "dark" + }, + { + "associatedValues" : [ + + ], + "name" : "auto", + "rawValue" : "auto" + } + ], + "emitStyle" : "const", + "name" : "Theme", + "rawType" : "String", + "swiftCallName" : "Theme" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "ok", + "rawValue" : "200" + }, + { + "associatedValues" : [ + + ], + "name" : "notFound", + "rawValue" : "404" + }, + { + "associatedValues" : [ + + ], + "name" : "serverError", + "rawValue" : "500" + } + ], + "emitStyle" : "const", + "name" : "HttpStatus", + "rawType" : "Int", + "swiftCallName" : "HttpStatus" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "north" + }, + { + "associatedValues" : [ + + ], + "name" : "south" + }, + { + "associatedValues" : [ + + ], + "name" : "east" + }, + { + "associatedValues" : [ + + ], + "name" : "west" + } + ], + "emitStyle" : "tsEnum", + "name" : "TSDirection", + "swiftCallName" : "TSDirection" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "light", + "rawValue" : "light" + }, + { + "associatedValues" : [ + + ], + "name" : "dark", + "rawValue" : "dark" + }, + { + "associatedValues" : [ + + ], + "name" : "auto", + "rawValue" : "auto" + } + ], + "emitStyle" : "tsEnum", + "name" : "TSTheme", + "rawType" : "String", + "swiftCallName" : "TSTheme" + }, + { + "cases" : [ + + ], + "emitStyle" : "const", + "name" : "Utils", + "swiftCallName" : "Utils" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "get" + }, + { + "associatedValues" : [ + + ], + "name" : "post" + }, + { + "associatedValues" : [ + + ], + "name" : "put" + }, + { + "associatedValues" : [ + + ], + "name" : "delete" + } + ], + "emitStyle" : "const", + "name" : "Method", + "namespace" : [ + "Networking", + "API" + ], + "swiftCallName" : "Networking.API.Method" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "debug", + "rawValue" : "debug" + }, + { + "associatedValues" : [ + + ], + "name" : "info", + "rawValue" : "info" + }, + { + "associatedValues" : [ + + ], + "name" : "warning", + "rawValue" : "warning" + }, + { + "associatedValues" : [ + + ], + "name" : "error", + "rawValue" : "error" + } + ], + "emitStyle" : "const", + "name" : "LogLevel", + "namespace" : [ + "Configuration" + ], + "rawType" : "String", + "swiftCallName" : "Configuration.LogLevel" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "http", + "rawValue" : "80" + }, + { + "associatedValues" : [ + + ], + "name" : "https", + "rawValue" : "443" + }, + { + "associatedValues" : [ + + ], + "name" : "development", + "rawValue" : "3000" + } + ], + "emitStyle" : "const", + "name" : "Port", + "namespace" : [ + "Configuration" + ], + "rawType" : "Int", + "swiftCallName" : "Configuration.Port" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "get" + }, + { + "associatedValues" : [ + + ], + "name" : "post" + } + ], + "emitStyle" : "const", + "name" : "SupportedMethod", + "namespace" : [ + "Networking", + "APIV2", + "Internal" + ], + "swiftCallName" : "Internal.SupportedMethod" } ], "functions" : [ { - "abiName" : "bjs_roundTripVoid", + "abiName" : "bjs_roundTripVoid", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "roundTripVoid", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_roundTripInt", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "roundTripInt", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "int" : { + + } + } + }, + { + "abiName" : "bjs_roundTripFloat", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "roundTripFloat", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "float" : { + + } + } + } + ], + "returnType" : { + "float" : { + + } + } + }, + { + "abiName" : "bjs_roundTripDouble", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "roundTripDouble", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + } + }, + { + "abiName" : "bjs_roundTripBool", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "roundTripBool", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "bool" : { + + } + } + } + ], + "returnType" : { + "bool" : { + + } + } + }, + { + "abiName" : "bjs_roundTripString", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "roundTripString", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_roundTripSwiftHeapObject", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "roundTripSwiftHeapObject", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + } + }, + { + "abiName" : "bjs_roundTripJSObject", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "roundTripJSObject", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "jsObject" : { + + } + } + } + ], + "returnType" : { + "jsObject" : { + + } + } + }, + { + "abiName" : "bjs_throwsSwiftError", + "effects" : { + "isAsync" : false, + "isThrows" : true + }, + "name" : "throwsSwiftError", + "parameters" : [ + { + "label" : "shouldThrow", + "name" : "shouldThrow", + "type" : { + "bool" : { + + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_throwsWithIntResult", + "effects" : { + "isAsync" : false, + "isThrows" : true + }, + "name" : "throwsWithIntResult", + "parameters" : [ + + ], + "returnType" : { + "int" : { + + } + } + }, + { + "abiName" : "bjs_throwsWithStringResult", + "effects" : { + "isAsync" : false, + "isThrows" : true + }, + "name" : "throwsWithStringResult", + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_throwsWithBoolResult", + "effects" : { + "isAsync" : false, + "isThrows" : true + }, + "name" : "throwsWithBoolResult", + "parameters" : [ + + ], + "returnType" : { + "bool" : { + + } + } + }, + { + "abiName" : "bjs_throwsWithFloatResult", + "effects" : { + "isAsync" : false, + "isThrows" : true + }, + "name" : "throwsWithFloatResult", + "parameters" : [ + + ], + "returnType" : { + "float" : { + + } + } + }, + { + "abiName" : "bjs_throwsWithDoubleResult", + "effects" : { + "isAsync" : false, + "isThrows" : true + }, + "name" : "throwsWithDoubleResult", + "parameters" : [ + + ], + "returnType" : { + "double" : { + + } + } + }, + { + "abiName" : "bjs_throwsWithSwiftHeapObjectResult", + "effects" : { + "isAsync" : false, + "isThrows" : true + }, + "name" : "throwsWithSwiftHeapObjectResult", + "parameters" : [ + + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + } + }, + { + "abiName" : "bjs_throwsWithJSObjectResult", + "effects" : { + "isAsync" : false, + "isThrows" : true + }, + "name" : "throwsWithJSObjectResult", + "parameters" : [ + + ], + "returnType" : { + "jsObject" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripVoid", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripVoid", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripInt", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripInt", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "int" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripFloat", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripFloat", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "float" : { + + } + } + } + ], + "returnType" : { + "float" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripDouble", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripDouble", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripBool", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripBool", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "bool" : { + + } + } + } + ], + "returnType" : { + "bool" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripString", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripString", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripSwiftHeapObject", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripSwiftHeapObject", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + } + }, + { + "abiName" : "bjs_asyncRoundTripJSObject", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripJSObject", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "jsObject" : { + + } + } + } + ], + "returnType" : { + "jsObject" : { + + } + } + }, + { + "abiName" : "bjs_takeGreeter", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "takeGreeter", + "parameters" : [ + { + "label" : "g", + "name" : "g", + "type" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + } + }, + { + "label" : "name", + "name" : "name", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_createCalculator", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "createCalculator", + "parameters" : [ + + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "Calculator" + } + } + }, + { + "abiName" : "bjs_useCalculator", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "useCalculator", + "parameters" : [ + { + "label" : "calc", + "name" : "calc", + "type" : { + "swiftHeapObject" : { + "_0" : "Calculator" + } + } + }, + { + "label" : "x", + "name" : "x", + "type" : { + "int" : { + + } + } + }, + { + "label" : "y", + "name" : "y", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "int" : { + + } + } + }, + { + "abiName" : "bjs_testGreeterToJSValue", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "testGreeterToJSValue", + "parameters" : [ + + ], + "returnType" : { + "jsObject" : { + + } + } + }, + { + "abiName" : "bjs_testCalculatorToJSValue", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "testCalculatorToJSValue", + "parameters" : [ + + ], + "returnType" : { + "jsObject" : { + + } + } + }, + { + "abiName" : "bjs_testSwiftClassAsJSValue", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "testSwiftClassAsJSValue", + "parameters" : [ + { + "label" : "greeter", + "name" : "greeter", + "type" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + } + } + ], + "returnType" : { + "jsObject" : { + + } + } + }, + { + "abiName" : "bjs_setDirection", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setDirection", + "parameters" : [ + { + "label" : "_", + "name" : "direction", + "type" : { + "caseEnum" : { + "_0" : "Direction" + } + } + } + ], + "returnType" : { + "caseEnum" : { + "_0" : "Direction" + } + } + }, + { + "abiName" : "bjs_getDirection", "effects" : { "isAsync" : false, "isThrows" : false }, - "name" : "roundTripVoid", + "name" : "getDirection", "parameters" : [ ], "returnType" : { - "void" : { - + "caseEnum" : { + "_0" : "Direction" } } }, { - "abiName" : "bjs_roundTripInt", + "abiName" : "bjs_processDirection", "effects" : { "isAsync" : false, "isThrows" : false }, - "name" : "roundTripInt", + "name" : "processDirection", "parameters" : [ { - "label" : "v", - "name" : "v", + "label" : "_", + "name" : "input", "type" : { - "int" : { - + "caseEnum" : { + "_0" : "Direction" } } } ], "returnType" : { - "int" : { - + "caseEnum" : { + "_0" : "Status" } } }, { - "abiName" : "bjs_roundTripFloat", + "abiName" : "bjs_setTheme", "effects" : { "isAsync" : false, "isThrows" : false }, - "name" : "roundTripFloat", + "name" : "setTheme", "parameters" : [ { - "label" : "v", - "name" : "v", + "label" : "_", + "name" : "theme", "type" : { - "float" : { - + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" } } } ], "returnType" : { - "float" : { - + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" } } }, { - "abiName" : "bjs_roundTripDouble", + "abiName" : "bjs_getTheme", "effects" : { "isAsync" : false, "isThrows" : false }, - "name" : "roundTripDouble", + "name" : "getTheme", "parameters" : [ - { - "label" : "v", - "name" : "v", - "type" : { - "double" : { - } - } - } ], "returnType" : { - "double" : { - + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" } } }, { - "abiName" : "bjs_roundTripBool", + "abiName" : "bjs_setHttpStatus", "effects" : { "isAsync" : false, "isThrows" : false }, - "name" : "roundTripBool", + "name" : "setHttpStatus", "parameters" : [ { - "label" : "v", - "name" : "v", + "label" : "_", + "name" : "status", "type" : { - "bool" : { - + "rawValueEnum" : { + "_0" : "HttpStatus", + "_1" : "Int" } } } ], "returnType" : { - "bool" : { - + "rawValueEnum" : { + "_0" : "HttpStatus", + "_1" : "Int" } } }, { - "abiName" : "bjs_roundTripString", + "abiName" : "bjs_getHttpStatus", "effects" : { "isAsync" : false, "isThrows" : false }, - "name" : "roundTripString", + "name" : "getHttpStatus", "parameters" : [ - { - "label" : "v", - "name" : "v", - "type" : { - "string" : { - } - } - } ], "returnType" : { - "string" : { - + "rawValueEnum" : { + "_0" : "HttpStatus", + "_1" : "Int" } } }, { - "abiName" : "bjs_roundTripSwiftHeapObject", + "abiName" : "bjs_processTheme", "effects" : { "isAsync" : false, "isThrows" : false }, - "name" : "roundTripSwiftHeapObject", + "name" : "processTheme", "parameters" : [ { - "label" : "v", - "name" : "v", + "label" : "_", + "name" : "theme", "type" : { - "swiftHeapObject" : { - "_0" : "Greeter" + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" } } } ], "returnType" : { - "swiftHeapObject" : { - "_0" : "Greeter" + "rawValueEnum" : { + "_0" : "HttpStatus", + "_1" : "Int" } } }, { - "abiName" : "bjs_roundTripJSObject", + "abiName" : "bjs_setTSDirection", "effects" : { "isAsync" : false, "isThrows" : false }, - "name" : "roundTripJSObject", + "name" : "setTSDirection", "parameters" : [ { - "label" : "v", - "name" : "v", + "label" : "_", + "name" : "direction", "type" : { - "jsObject" : { - + "caseEnum" : { + "_0" : "TSDirection" } } } ], "returnType" : { - "jsObject" : { + "caseEnum" : { + "_0" : "TSDirection" + } + } + }, + { + "abiName" : "bjs_getTSDirection", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getTSDirection", + "parameters" : [ + ], + "returnType" : { + "caseEnum" : { + "_0" : "TSDirection" } } }, { - "abiName" : "bjs_throwsSwiftError", + "abiName" : "bjs_setTSTheme", "effects" : { "isAsync" : false, - "isThrows" : true + "isThrows" : false }, - "name" : "throwsSwiftError", + "name" : "setTSTheme", "parameters" : [ { - "label" : "shouldThrow", - "name" : "shouldThrow", + "label" : "_", + "name" : "theme", "type" : { - "bool" : { - + "rawValueEnum" : { + "_0" : "TSTheme", + "_1" : "String" } } } ], "returnType" : { - "void" : { - + "rawValueEnum" : { + "_0" : "TSTheme", + "_1" : "String" } } }, { - "abiName" : "bjs_throwsWithIntResult", + "abiName" : "bjs_getTSTheme", "effects" : { "isAsync" : false, - "isThrows" : true + "isThrows" : false }, - "name" : "throwsWithIntResult", + "name" : "getTSTheme", "parameters" : [ ], "returnType" : { - "int" : { - + "rawValueEnum" : { + "_0" : "TSTheme", + "_1" : "String" } } }, { - "abiName" : "bjs_throwsWithStringResult", + "abiName" : "bjs_echoNetworkingAPIMethod", "effects" : { "isAsync" : false, - "isThrows" : true + "isThrows" : false }, - "name" : "throwsWithStringResult", + "name" : "echoNetworkingAPIMethod", "parameters" : [ - + { + "label" : "_", + "name" : "method", + "type" : { + "caseEnum" : { + "_0" : "Networking.API.Method" + } + } + } ], "returnType" : { - "string" : { - + "caseEnum" : { + "_0" : "Networking.API.Method" } } }, { - "abiName" : "bjs_throwsWithBoolResult", + "abiName" : "bjs_echoConfigurationLogLevel", "effects" : { "isAsync" : false, - "isThrows" : true + "isThrows" : false }, - "name" : "throwsWithBoolResult", + "name" : "echoConfigurationLogLevel", "parameters" : [ - + { + "label" : "_", + "name" : "level", + "type" : { + "rawValueEnum" : { + "_0" : "Configuration.LogLevel", + "_1" : "String" + } + } + } ], "returnType" : { - "bool" : { - + "rawValueEnum" : { + "_0" : "Configuration.LogLevel", + "_1" : "String" } } }, { - "abiName" : "bjs_throwsWithFloatResult", + "abiName" : "bjs_echoConfigurationPort", "effects" : { "isAsync" : false, - "isThrows" : true + "isThrows" : false }, - "name" : "throwsWithFloatResult", + "name" : "echoConfigurationPort", "parameters" : [ - + { + "label" : "_", + "name" : "port", + "type" : { + "rawValueEnum" : { + "_0" : "Configuration.Port", + "_1" : "Int" + } + } + } ], "returnType" : { - "float" : { - + "rawValueEnum" : { + "_0" : "Configuration.Port", + "_1" : "Int" } } }, { - "abiName" : "bjs_throwsWithDoubleResult", + "abiName" : "bjs_processConfigurationLogLevel", "effects" : { "isAsync" : false, - "isThrows" : true + "isThrows" : false }, - "name" : "throwsWithDoubleResult", + "name" : "processConfigurationLogLevel", "parameters" : [ - + { + "label" : "_", + "name" : "level", + "type" : { + "rawValueEnum" : { + "_0" : "Configuration.LogLevel", + "_1" : "String" + } + } + } ], "returnType" : { - "double" : { - + "rawValueEnum" : { + "_0" : "Configuration.Port", + "_1" : "Int" } } }, { - "abiName" : "bjs_throwsWithSwiftHeapObjectResult", + "abiName" : "bjs_echoInternalSupportedMethod", "effects" : { "isAsync" : false, - "isThrows" : true + "isThrows" : false }, - "name" : "throwsWithSwiftHeapObjectResult", + "name" : "echoInternalSupportedMethod", "parameters" : [ - + { + "label" : "_", + "name" : "method", + "type" : { + "caseEnum" : { + "_0" : "Internal.SupportedMethod" + } + } + } ], "returnType" : { - "swiftHeapObject" : { - "_0" : "Greeter" + "caseEnum" : { + "_0" : "Internal.SupportedMethod" } } }, { - "abiName" : "bjs_throwsWithJSObjectResult", + "abiName" : "bjs_createPropertyHolder", "effects" : { "isAsync" : false, - "isThrows" : true + "isThrows" : false }, - "name" : "throwsWithJSObjectResult", + "name" : "createPropertyHolder", "parameters" : [ + { + "label" : "intValue", + "name" : "intValue", + "type" : { + "int" : { + + } + } + }, + { + "label" : "floatValue", + "name" : "floatValue", + "type" : { + "float" : { + + } + } + }, + { + "label" : "doubleValue", + "name" : "doubleValue", + "type" : { + "double" : { + + } + } + }, + { + "label" : "boolValue", + "name" : "boolValue", + "type" : { + "bool" : { + + } + } + }, + { + "label" : "stringValue", + "name" : "stringValue", + "type" : { + "string" : { + + } + } + }, + { + "label" : "jsObject", + "name" : "jsObject", + "type" : { + "jsObject" : { + } + } + } ], "returnType" : { - "jsObject" : { - + "swiftHeapObject" : { + "_0" : "PropertyHolder" } } }, { - "abiName" : "bjs_takeGreeter", + "abiName" : "bjs_testPropertyHolder", "effects" : { "isAsync" : false, "isThrows" : false }, - "name" : "takeGreeter", + "name" : "testPropertyHolder", "parameters" : [ { - "label" : "g", - "name" : "g", + "label" : "holder", + "name" : "holder", "type" : { "swiftHeapObject" : { - "_0" : "Greeter" + "_0" : "PropertyHolder" } } - }, - { - "label" : "name", - "name" : "name", - "type" : { - "string" : { + } + ], + "returnType" : { + "string" : { - } - } } + } + }, + { + "abiName" : "bjs_resetObserverCounts", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "resetObserverCounts", + "parameters" : [ + ], "returnType" : { "void" : { + } + } + }, + { + "abiName" : "bjs_getObserverStats", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getObserverStats", + "parameters" : [ + + ], + "returnType" : { + "string" : { + } } } - ] + ], + "moduleName" : "BridgeJSRuntimeTests" } \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ImportTS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ImportTS.json index bf3190b8..82515fec 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ImportTS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ImportTS.json @@ -138,6 +138,17 @@ } } + }, + { + "name" : "runAsyncWorks", + "parameters" : [ + + ], + "returnType" : { + "jsObject" : { + "_0" : "JSPromise" + } + } } ], "types" : [ diff --git a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts index b03ef570..0f0d0155 100644 --- a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts +++ b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts @@ -14,3 +14,5 @@ export class JsGreeter { greet(): string; changeName(name: string): void; } + +export function runAsyncWorks(): Promise; diff --git a/Tests/JavaScriptEventLoopTests/WebWorkerDedicatedExecutorTests.swift b/Tests/JavaScriptEventLoopTests/WebWorkerDedicatedExecutorTests.swift index b6c2bd8d..aae8c2ce 100644 --- a/Tests/JavaScriptEventLoopTests/WebWorkerDedicatedExecutorTests.swift +++ b/Tests/JavaScriptEventLoopTests/WebWorkerDedicatedExecutorTests.swift @@ -2,6 +2,7 @@ import XCTest @testable import JavaScriptEventLoop +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) final class WebWorkerDedicatedExecutorTests: XCTestCase { actor MyActor { let executor: WebWorkerDedicatedExecutor diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index c6ac428a..8094a465 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -1,64 +1,78 @@ // @ts-check +import { + Direction, Status, Theme, HttpStatus, TSDirection, TSTheme +} from '../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.js'; + /** @type {import('../.build/plugins/PackageToJS/outputs/PackageTests/test.d.ts').SetupOptionsFn} */ export async 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; - }, - "jsThrowOrVoid": (shouldThrow) => { - if (shouldThrow) { - throw new Error("TestError"); - } - }, - "jsThrowOrNumber": (shouldThrow) => { - if (shouldThrow) { - throw new Error("TestError"); - } - return 1; - }, - "jsThrowOrBool": (shouldThrow) => { - if (shouldThrow) { - throw new Error("TestError"); - } - return true; - }, - "jsThrowOrString": (shouldThrow) => { - if (shouldThrow) { - throw new Error("TestError"); - } - return "Hello, world!"; - }, - JsGreeter: class { - /** - * @param {string} name - * @param {string} prefix - */ - constructor(name, prefix) { - this.name = name; - this.prefix = prefix; - } - greet() { - return `${this.prefix}, ${this.name}!`; - } - /** @param {string} name */ - changeName(name) { - this.name = name; + getImports: (importsContext) => { + return { + "jsRoundTripVoid": () => { + return; + }, + "jsRoundTripNumber": (v) => { + return v; + }, + "jsRoundTripBool": (v) => { + return v; + }, + "jsRoundTripString": (v) => { + return v; + }, + "jsThrowOrVoid": (shouldThrow) => { + if (shouldThrow) { + throw new Error("TestError"); + } + }, + "jsThrowOrNumber": (shouldThrow) => { + if (shouldThrow) { + throw new Error("TestError"); + } + return 1; + }, + "jsThrowOrBool": (shouldThrow) => { + if (shouldThrow) { + throw new Error("TestError"); + } + return true; + }, + "jsThrowOrString": (shouldThrow) => { + if (shouldThrow) { + throw new Error("TestError"); + } + return "Hello, world!"; + }, + JsGreeter: class { + /** + * @param {string} name + * @param {string} prefix + */ + constructor(name, prefix) { + this.name = name; + this.prefix = prefix; + } + greet() { + return `${this.prefix}, ${this.name}!`; + } + /** @param {string} name */ + changeName(name) { + this.name = name; + } + }, + runAsyncWorks: async () => { + const exports = importsContext.getExports(); + if (!exports) { + throw new Error("No exports!?"); + } + BridgeJSRuntimeTests_runAsyncWorks(exports); + return; } - } + }; }, addToCoreImports(importObject, importsContext) { const { getInstance, getExports } = importsContext; @@ -68,7 +82,11 @@ export async function setupOptions(options, context) { } const bridgeJSRuntimeTests = importObject["BridgeJSRuntimeTests"] || {}; bridgeJSRuntimeTests["runJsWorks"] = () => { - return BridgeJSRuntimeTests_runJsWorks(getInstance(), getExports()); + const exports = getExports(); + if (!exports) { + throw new Error("No exports!?"); + } + return BridgeJSRuntimeTests_runJsWorks(getInstance(), exports); } importObject["BridgeJSRuntimeTests"] = bridgeJSRuntimeTests; } @@ -116,16 +134,154 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { } const g = new exports.Greeter("John"); - const g2 = exports.roundTripSwiftHeapObject(g) - g2.release(); - assert.equal(g.greet(), "Hello, John!"); - g.changeName("Jane"); + + // Test property getters + assert.equal(g.name, "John"); + assert.equal(g.prefix, "Hello"); + + // Test property setters + g.name = "Jane"; + assert.equal(g.name, "Jane"); assert.equal(g.greet(), "Hello, Jane!"); - exports.takeGreeter(g, "Jay"); + + // Test readonly property (should still be readable) + assert.equal(g.prefix, "Hello"); + + // Test method-based name change still works + g.changeName("Jay"); + assert.equal(g.name, "Jay"); assert.equal(g.greet(), "Hello, Jay!"); + + // Test that property changes via JS are reflected in Swift methods + g.name = "Alice"; + assert.equal(g.greet(), "Hello, Alice!"); + exports.takeGreeter(g, "Bob"); + assert.equal(g.name, "Bob"); + assert.equal(g.greet(), "Hello, Bob!"); + + const g2 = exports.roundTripSwiftHeapObject(g) + assert.equal(g2.greet(), "Hello, Bob!"); + assert.equal(g2.name, "Bob"); + assert.equal(g2.prefix, "Hello"); + g2.release(); + g.release(); + // Test PropertyHolder with various types + const testObj = { testProp: "test" }; + const sibling = new exports.SimplePropertyHolder(999); + const ph = new exports.PropertyHolder( + 123, // intValue + 3.14, // floatValue + 2.718, // doubleValue + true, // boolValue + "test", // stringValue + testObj, // jsObject + sibling // sibling + ); + + // Test primitive property getters + assert.equal(ph.intValue, 123); + assert.equal(ph.floatValue, 3.140000104904175); // Float32 precision + assert.equal(ph.doubleValue, 2.718); + assert.equal(ph.boolValue, true); + assert.equal(ph.stringValue, "test"); + + // Test readonly property getters + assert.equal(ph.readonlyInt, 42); + assert.equal(ph.readonlyFloat, 3.140000104904175); // Float32 precision + assert.equal(ph.readonlyDouble, 2.718281828); + assert.equal(ph.readonlyBool, true); + assert.equal(ph.readonlyString, "constant"); + + // Test JSObject property + assert.equal(ph.jsObject, testObj); + + // Test SwiftHeapObject property (sibling should be a SimplePropertyHolder with value 999) + assert.equal(ph.sibling.value, 999); + + // Test primitive property setters + ph.intValue = 456; + ph.floatValue = 6.28; + ph.doubleValue = 1.414; + ph.boolValue = false; + ph.stringValue = "updated"; + + assert.equal(ph.intValue, 456); + assert.equal(ph.floatValue, 6.280000209808350); // Float32 precision + assert.equal(ph.doubleValue, 1.414); + assert.equal(ph.boolValue, false); + assert.equal(ph.stringValue, "updated"); + + // Test JSObject property setter + const newObj = { newProp: "new" }; + ph.jsObject = newObj; + assert.equal(ph.jsObject, newObj); + + // Test SwiftHeapObject property with different object + const ph2 = exports.createPropertyHolder(999, 1.1, 2.2, false, "other", testObj); + const newSibling = new exports.SimplePropertyHolder(123); + ph.sibling = newSibling; + assert.equal(ph.sibling.value, 123); + + // Test lazy property + assert.equal(ph.lazyValue, "computed lazily"); + ph.lazyValue = "modified lazy"; + assert.equal(ph.lazyValue, "modified lazy"); + + // Test computed read-write property + assert.equal(ph.computedReadWrite, "Value: 456"); + ph.computedReadWrite = "Value: 777"; + assert.equal(ph.intValue, 777); // Should have parsed and set intValue + assert.equal(ph.computedReadWrite, "Value: 777"); + + // Test computed readonly property + assert.equal(ph.computedReadonly, 1554); // intValue * 2 = 777 * 2 + + // Test property with observers + // Sync observedProperty to match current intValue, then reset counters for clean test + ph.observedProperty = 777; // Sync with current intValue after computed property changed it + exports.resetObserverCounts(); // Reset counters to start fresh test + + // Set property from JavaScript and verify observers are called + ph.observedProperty = 100; + assert.equal(ph.observedProperty, 100); + let afterSetStats = exports.getObserverStats(); + + // Verify willSet and didSet were called + // The stats should show: willSet:1,didSet:1,willSetOld:777,willSetNew:100,didSetOld:777,didSetNew:100 + assert(afterSetStats.includes("willSet:1"), `willSet should be called once, got: ${afterSetStats}`); + assert(afterSetStats.includes("didSet:1"), `didSet should be called once, got: ${afterSetStats}`); + assert(afterSetStats.includes("willSetOld:777"), `willSet should see old value 777, got: ${afterSetStats}`); + assert(afterSetStats.includes("willSetNew:100"), `willSet should see new value 100, got: ${afterSetStats}`); + assert(afterSetStats.includes("didSetOld:777"), `didSet should see old value 777, got: ${afterSetStats}`); + assert(afterSetStats.includes("didSetNew:100"), `didSet should see new value 100, got: ${afterSetStats}`); + + // Set property to a different value and verify observers are called again + ph.observedProperty = 200; + assert.equal(ph.observedProperty, 200); + let afterSecondSetStats = exports.getObserverStats(); + + // Now should be: willSet:2,didSet:2,willSetOld:100,willSetNew:200,didSetOld:100,didSetNew:200 + assert(afterSecondSetStats.includes("willSet:2"), `willSet should be called twice, got: ${afterSecondSetStats}`); + assert(afterSecondSetStats.includes("didSet:2"), `didSet should be called twice, got: ${afterSecondSetStats}`); + assert(afterSecondSetStats.includes("willSetOld:100"), `willSet should see old value 100 on second call, got: ${afterSecondSetStats}`); + assert(afterSecondSetStats.includes("willSetNew:200"), `willSet should see new value 200 on second call, got: ${afterSecondSetStats}`); + assert(afterSecondSetStats.includes("didSetOld:100"), `didSet should see old value 100 on second call, got: ${afterSecondSetStats}`); + assert(afterSecondSetStats.includes("didSetNew:200"), `didSet should see new value 200 on second call, got: ${afterSecondSetStats}`); + + ph.release(); + ph2.release(); + + // Test class without @JS init constructor + const calc = exports.createCalculator(); + assert.equal(calc.square(5), 25); + assert.equal(calc.add(3, 4), 7); + assert.equal(exports.useCalculator(calc, 3, 10), 19); // 3^2 + 10 = 19 + + calc.release(); + const anyObject = {}; assert.equal(exports.roundTripJSObject(anyObject), anyObject); @@ -141,6 +297,103 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { } catch (error) { assert.fail("Expected no error"); } + + assert.equal(Direction.North, 0); + assert.equal(Direction.South, 1); + assert.equal(Direction.East, 2); + assert.equal(Direction.West, 3); + assert.equal(Status.Loading, 0); + assert.equal(Status.Success, 1); + assert.equal(Status.Error, 2); + + assert.equal(exports.setDirection(Direction.North), Direction.North); + assert.equal(exports.setDirection(Direction.South), Direction.South); + assert.equal(exports.getDirection(), Direction.North); + assert.equal(exports.processDirection(Direction.North), Status.Success); + assert.equal(exports.processDirection(Direction.East), Status.Loading); + + assert.equal(Theme.Light, "light"); + assert.equal(Theme.Dark, "dark"); + assert.equal(Theme.Auto, "auto"); + assert.equal(HttpStatus.Ok, 200); + assert.equal(HttpStatus.NotFound, 404); + assert.equal(HttpStatus.ServerError, 500); + + assert.equal(exports.setTheme(Theme.Light), Theme.Light); + assert.equal(exports.setTheme(Theme.Dark), Theme.Dark); + assert.equal(exports.getTheme(), Theme.Light); + assert.equal(exports.setHttpStatus(HttpStatus.Ok), HttpStatus.Ok); + assert.equal(exports.getHttpStatus(), HttpStatus.Ok); + assert.equal(exports.processTheme(Theme.Light), HttpStatus.Ok); + assert.equal(exports.processTheme(Theme.Dark), HttpStatus.NotFound); + + assert.equal(TSDirection.North, 0); + assert.equal(TSDirection.South, 1); + assert.equal(TSDirection.East, 2); + assert.equal(TSDirection.West, 3); + assert.equal(TSTheme.Light, "light"); + assert.equal(TSTheme.Dark, "dark"); + assert.equal(TSTheme.Auto, "auto"); + + assert.equal(exports.setTSDirection(TSDirection.North), TSDirection.North); + assert.equal(exports.getTSDirection(), TSDirection.North); + assert.equal(exports.setTSTheme(TSTheme.Light), TSTheme.Light); + assert.equal(exports.getTSTheme(), TSTheme.Light); + + assert.equal(globalThis.Networking.API.Method.Get, 0); + assert.equal(globalThis.Networking.API.Method.Post, 1); + assert.equal(globalThis.Networking.API.Method.Put, 2); + assert.equal(globalThis.Networking.API.Method.Delete, 3); + assert.equal(globalThis.Configuration.LogLevel.Debug, "debug"); + assert.equal(globalThis.Configuration.LogLevel.Info, "info"); + assert.equal(globalThis.Configuration.LogLevel.Warning, "warning"); + assert.equal(globalThis.Configuration.LogLevel.Error, "error"); + assert.equal(globalThis.Configuration.Port.Http, 80); + assert.equal(globalThis.Configuration.Port.Https, 443); + assert.equal(globalThis.Configuration.Port.Development, 3000); + assert.equal(globalThis.Networking.APIV2.Internal.SupportedMethod.Get, 0); + assert.equal(globalThis.Networking.APIV2.Internal.SupportedMethod.Post, 1); + + assert.equal(exports.echoNetworkingAPIMethod(globalThis.Networking.API.Method.Get), globalThis.Networking.API.Method.Get); + assert.equal(exports.echoConfigurationLogLevel(globalThis.Configuration.LogLevel.Debug), globalThis.Configuration.LogLevel.Debug); + assert.equal(exports.echoConfigurationPort(globalThis.Configuration.Port.Http), globalThis.Configuration.Port.Http); + assert.equal(exports.processConfigurationLogLevel(globalThis.Configuration.LogLevel.Debug), globalThis.Configuration.Port.Development); + assert.equal(exports.processConfigurationLogLevel(globalThis.Configuration.LogLevel.Info), globalThis.Configuration.Port.Http); + assert.equal(exports.processConfigurationLogLevel(globalThis.Configuration.LogLevel.Warning), globalThis.Configuration.Port.Https); + assert.equal(exports.processConfigurationLogLevel(globalThis.Configuration.LogLevel.Error), globalThis.Configuration.Port.Development); + assert.equal(exports.echoInternalSupportedMethod(globalThis.Networking.APIV2.Internal.SupportedMethod.Get), globalThis.Networking.APIV2.Internal.SupportedMethod.Get); + + const converter = new exports.Converter(); + assert.equal(converter.toString(42), "42"); + assert.equal(converter.toString(123), "123"); + converter.release(); + + const httpServer = new exports.HTTPServer(); + httpServer.call(globalThis.Networking.API.Method.Get); + httpServer.call(globalThis.Networking.API.Method.Post); + httpServer.release(); + + const testServer = new exports.TestServer(); + testServer.call(globalThis.Networking.APIV2.Internal.SupportedMethod.Get); + testServer.call(globalThis.Networking.APIV2.Internal.SupportedMethod.Post); + testServer.release(); + + const globalConverter = new globalThis.Utils.Converter(); + assert.equal(globalConverter.toString(99), "99"); + globalConverter.release(); + + const globalHttpServer = new globalThis.Networking.API.HTTPServer(); + globalHttpServer.call(globalThis.Networking.API.Method.Get); + globalHttpServer.release(); + + const globalTestServer = new globalThis.Networking.APIV2.Internal.TestServer(); + globalTestServer.call(globalThis.Networking.APIV2.Internal.SupportedMethod.Post); + globalTestServer.release(); +} + +/** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */ +async function BridgeJSRuntimeTests_runAsyncWorks(exports) { + await exports.asyncRoundTripVoid(); } function setupTestGlobals(global) {