From 17586955796c811a5b4f11282d12f2fe84dc47b7 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 18 Feb 2026 20:10:56 +0900 Subject: [PATCH 1/3] TS2Swift: handle callable exported consts --- .../TS2Swift/JavaScript/src/processor.js | 19 ++++++++++++++++++- .../test/__snapshots__/ts2swift.test.js.snap | 13 +++++++++++++ .../test/fixtures/CallableConst.d.ts | 2 ++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/fixtures/CallableConst.d.ts diff --git a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js index 53216a78c..09158daa8 100644 --- a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js +++ b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js @@ -270,13 +270,30 @@ export class TypeProcessor { const swiftVarName = this.renderIdentifier(swiftName); const type = this.checker.getTypeAtLocation(decl); - const swiftType = this.visitType(type, decl); /** @type {string[]} */ const args = []; const jsNameArg = this.renderOptionalJSNameArg(jsName, swiftName); if (jsNameArg) args.push(jsNameArg); if (fromArg) args.push(fromArg); + const callSignatures = type.getCallSignatures(); + + if (callSignatures.length > 0) { + const signature = callSignatures[0]; + const parameters = signature.getParameters(); + const parameterNameMap = this.buildParameterNameMap(parameters); + const params = this.renderParameters(parameters, decl); + const returnType = this.visitType(signature.getReturnType(), decl); + const effects = this.renderEffects({ isAsync: false }); + const annotation = this.renderMacroAnnotation("JSFunction", args); + + this.emitDocComment(decl, { indent: "", parameterNameMap }); + this.swiftLines.push(`${annotation} func ${swiftVarName}(${params}) ${effects} -> ${returnType}`); + this.swiftLines.push(""); + continue; + } + + const swiftType = this.visitType(type, decl); const annotation = this.renderMacroAnnotation("JSGetter", args); this.emitDocComment(decl, { indent: "" }); diff --git a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/__snapshots__/ts2swift.test.js.snap b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/__snapshots__/ts2swift.test.js.snap index 85d1da0de..44f70aa77 100644 --- a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/__snapshots__/ts2swift.test.js.snap +++ b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/__snapshots__/ts2swift.test.js.snap @@ -48,6 +48,19 @@ exports[`ts2swift > snapshots Swift output for Async.d.ts > Async 1`] = ` " `; +exports[`ts2swift > snapshots Swift output for CallableConst.d.ts > CallableConst 1`] = ` +"// 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 + +@JSFunction func fetch(_ url: String) throws(JSException) -> JSPromise +" +`; + exports[`ts2swift > snapshots Swift output for Documentation.d.ts > Documentation 1`] = ` "// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, // DO NOT EDIT. diff --git a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/fixtures/CallableConst.d.ts b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/fixtures/CallableConst.d.ts new file mode 100644 index 000000000..3661cd05e --- /dev/null +++ b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/fixtures/CallableConst.d.ts @@ -0,0 +1,2 @@ +export interface Response {} +export const fetch: (url: string) => Promise; From 27c3d53055c7bcf15d4e19ab379914d1b84c7140 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 18 Feb 2026 20:13:14 +0900 Subject: [PATCH 2/3] TS2Swift tests: use non-Promise callable const fixture --- .../JavaScript/test/__snapshots__/ts2swift.test.js.snap | 5 ++++- .../TS2Swift/JavaScript/test/fixtures/CallableConst.d.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/__snapshots__/ts2swift.test.js.snap b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/__snapshots__/ts2swift.test.js.snap index 44f70aa77..223927700 100644 --- a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/__snapshots__/ts2swift.test.js.snap +++ b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/__snapshots__/ts2swift.test.js.snap @@ -57,7 +57,10 @@ exports[`ts2swift > snapshots Swift output for CallableConst.d.ts > CallableCons @_spi(BridgeJS) import JavaScriptKit -@JSFunction func fetch(_ url: String) throws(JSException) -> JSPromise +@JSFunction func fetch(_ url: String) throws(JSException) -> Response + +@JSClass struct Response { +} " `; diff --git a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/fixtures/CallableConst.d.ts b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/fixtures/CallableConst.d.ts index 3661cd05e..3df79d597 100644 --- a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/fixtures/CallableConst.d.ts +++ b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/fixtures/CallableConst.d.ts @@ -1,2 +1,2 @@ export interface Response {} -export const fetch: (url: string) => Promise; +export const fetch: (url: string) => Response; From fc7aff684e448253256998409f34d69691049de1 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 18 Feb 2026 20:15:34 +0900 Subject: [PATCH 3/3] TS2Swift: only const callable exports become JSFunction --- Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js index 09158daa8..ed332dd3c 100644 --- a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js +++ b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js @@ -261,6 +261,7 @@ export class TypeProcessor { if (!isExported) return; const fromArg = this.renderDefaultJSImportFromArgument(); + const isConst = (node.declarationList.flags & ts.NodeFlags.Const) !== 0; for (const decl of node.declarationList.declarations) { if (!ts.isIdentifier(decl.name)) continue; @@ -278,7 +279,7 @@ export class TypeProcessor { if (fromArg) args.push(fromArg); const callSignatures = type.getCallSignatures(); - if (callSignatures.length > 0) { + if (isConst && callSignatures.length > 0) { const signature = callSignatures[0]; const parameters = signature.getParameters(); const parameterNameMap = this.buildParameterNameMap(parameters);