Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
BridgeJS: Test custom exported type names
  • Loading branch information
krodak committed Sep 15, 2026
commit 1711b358e1b7a565b111fbbdb55105e5ffb5a266
36 changes: 0 additions & 36 deletions Plugins/BridgeJS/Tests/BridgeJSToolTests/DiagnosticsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -696,42 +696,6 @@ import Testing
}
}

@Test
func jsNameOnClassDiagnostic() throws {
let source = """
@JS("Renamed") class Box { @JS init() {} }
"""
let diagnostics = try #require(moduleDiagnostics(source: source))
#expect(diagnostics.description.contains("A separate name for JavaScript is not supported here"))
}

@Test
func jsNameOnStructDiagnostic() throws {
let source = """
@JS("Renamed") struct Box { var x: Int }
"""
let diagnostics = try #require(moduleDiagnostics(source: source))
#expect(diagnostics.description.contains("A separate name for JavaScript is not supported here"))
}

@Test
func jsNameOnEnumDiagnostic() throws {
let source = """
@JS("Renamed") enum Box { case a }
"""
let diagnostics = try #require(moduleDiagnostics(source: source))
#expect(diagnostics.description.contains("A separate name for JavaScript is not supported here"))
}

@Test
func jsNameOnProtocolDiagnostic() throws {
let source = """
@JS("Renamed") protocol Box { func run() }
"""
let diagnostics = try #require(moduleDiagnostics(source: source))
#expect(diagnostics.description.contains("A separate name for JavaScript is not supported here"))
}

@Test
func jsNameOnInitializerDiagnostic() throws {
let source = """
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import Foundation
import SwiftParser
import Testing

@testable import BridgeJSCore
@testable import BridgeJSSkeleton

@Suite struct ExportedTypeNameTests {
private func parse(_ source: String) throws -> ExportedSkeleton {
let generator = SwiftToSkeleton(
progress: .silent,
moduleName: "TestModule",
exposeToGlobal: false,
externalModuleIndex: .empty
)
generator.addSourceFile(Parser.parse(source: source), inputFilePath: "Types.swift")
return try #require(generator.finalize().exported)
}

@Test
func renamedTypesKeepSwiftIdentities() throws {
let exported = try parse(
"""
@JS func box() -> SwiftBox
@JS func record() -> SwiftRecord
@JS func choice() -> SwiftChoice
@JS func delegate(_ value: SwiftDelegate)
@JS("PublicBox") class SwiftBox { @JS init() {} }
@JS("PublicRecord") struct SwiftRecord { var value: Int }
@JS("PublicChoice") enum SwiftChoice { case first }
@JS("PublicDelegate") protocol SwiftDelegate { func run() }
"""
)
#expect(exported.classes.first?.resolvedJSName == "PublicBox")
#expect(exported.structs.first?.resolvedJSName == "PublicRecord")
#expect(exported.enums.first?.resolvedJSName == "PublicChoice")
#expect(exported.protocols.first?.resolvedJSName == "PublicDelegate")
#expect(exported.functions[0].returnType == .swiftHeapObject("SwiftBox"))
#expect(exported.functions[1].returnType == .swiftStruct("SwiftRecord"))
#expect(exported.functions[2].returnType == .caseEnum("SwiftChoice"))
#expect(exported.functions[3].parameters.first?.type == .swiftProtocol("SwiftDelegate"))
}

@Test
func renamedParentsKeepSwiftThunkABI() throws {
let source = """
@JS(namespace: "API") enum InternalAPI {
@JS class Item { @JS init() {} }
}
"""
let original = try parse(source)
let renamed = try parse(
source.replacingOccurrences(of: "@JS(namespace:", with: "@JS(\"PublicAPI\", namespace:")
)
let originalThunks = try ExportSwift(progress: .silent, moduleName: "TestModule", skeleton: original).finalize()
let renamedThunks = try ExportSwift(progress: .silent, moduleName: "TestModule", skeleton: renamed).finalize()
#expect(renamedThunks == originalThunks)
#expect(renamed.classes.first?.tsFullPath == "API.PublicAPI.Item")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@JS("PublicBox") final class SwiftBox {
@JS init()
@JS func copy() -> SwiftBox
}

@JS("PublicChoice") enum SwiftChoice {
case first
case second
}

@JS func renamedChoice(_ value: SwiftChoice) -> SwiftChoice
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
{
"exported" : {
"aliases" : [

],
"classes" : [
{
"constructor" : {
"abiName" : "bjs_SwiftBox_init",
"effects" : {
"isAsync" : false,
"isStatic" : false,
"isThrows" : false
},
"parameters" : [

]
},
"isFinal" : true,
"jsName" : "PublicBox",
"methods" : [
{
"abiName" : "bjs_SwiftBox_copy",
"effects" : {
"isAsync" : false,
"isStatic" : false,
"isThrows" : false
},
"name" : "copy",
"parameters" : [

],
"returnType" : {
"swiftHeapObject" : {
"_0" : "SwiftBox"
}
}
}
],
"name" : "SwiftBox",
"properties" : [

],
"swiftCallName" : "SwiftBox"
}
],
"enums" : [
{
"cases" : [
{
"associatedValues" : [

],
"name" : "first"
},
{
"associatedValues" : [

],
"name" : "second"
}
],
"emitStyle" : "const",
"jsName" : "PublicChoice",
"name" : "SwiftChoice",
"staticMethods" : [

],
"staticProperties" : [

],
"swiftCallName" : "SwiftChoice",
"tsFullPath" : "PublicChoice"
}
],
"exposeToGlobal" : false,
"functions" : [
{
"abiName" : "bjs_renamedChoice",
"effects" : {
"isAsync" : false,
"isStatic" : false,
"isThrows" : false
},
"name" : "renamedChoice",
"parameters" : [
{
"label" : "_",
"name" : "value",
"type" : {
"caseEnum" : {
"_0" : "SwiftChoice"
}
}
}
],
"returnType" : {
"caseEnum" : {
"_0" : "SwiftChoice"
}
}
}
],
"protocols" : [

],
"structs" : [

]
},
"moduleName" : "TestModule",
"usedExternalModules" : [

]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
extension SwiftChoice: _BridgedSwiftCaseEnum {
@_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 {
return bridgeJSRawValue
}
@_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> SwiftChoice {
return bridgeJSLiftParameter(value)
}
@_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> SwiftChoice {
return SwiftChoice(bridgeJSRawValue: value)!
}
@_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 {
return bridgeJSLowerParameter()
}

@_spi(BridgeJS) @usableFromInline init?(bridgeJSRawValue: Int32) {
switch bridgeJSRawValue {
case 0:
self = .first
case 1:
self = .second
default:
return nil
}
}

@_spi(BridgeJS) @usableFromInline var bridgeJSRawValue: Int32 {
switch self {
case .first:
return 0
case .second:
return 1
}
}
}

@_expose(wasm, "bjs_renamedChoice")
@_cdecl("bjs_renamedChoice")
public func _bjs_renamedChoice(_ value: Int32) -> Int32 {
#if arch(wasm32)
let value = SwiftChoice.bridgeJSLiftParameter(value)
let ret = renamedChoice(_: value)
return ret.bridgeJSLowerReturn()
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_SwiftBox_init")
@_cdecl("bjs_SwiftBox_init")
public func _bjs_SwiftBox_init() -> UnsafeMutableRawPointer {
#if arch(wasm32)
let ret = SwiftBox()
return ret.bridgeJSLowerReturn()
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_SwiftBox_copy")
@_cdecl("bjs_SwiftBox_copy")
public func _bjs_SwiftBox_copy(_ _self: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer {
#if arch(wasm32)
let _self = SwiftBox.bridgeJSLiftParameter(_self)
let ret = _self.copy()
return ret.bridgeJSLowerReturn()
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_SwiftBox_deinit")
@_cdecl("bjs_SwiftBox_deinit")
public func _bjs_SwiftBox_deinit(_ pointer: UnsafeMutableRawPointer) -> Void {
#if arch(wasm32)
Unmanaged<SwiftBox>.fromOpaque(pointer).release()
#else
fatalError("Only available on WebAssembly")
#endif
}

extension SwiftBox: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable {
var jsValue: JSValue {
return .object(JSObject(id: UInt32(bitPattern: _bjs_SwiftBox_wrap(Unmanaged.passRetained(self).toOpaque()))))
}
consuming func bridgeJSLowerAsProtocolReturn() -> Int32 {
_bjs_SwiftBox_wrap(Unmanaged.passRetained(self).toOpaque())
}
}

#if arch(wasm32)
@_extern(wasm, module: "TestModule", name: "bjs_SwiftBox_wrap")
fileprivate func _bjs_SwiftBox_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32
#else
fileprivate func _bjs_SwiftBox_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 {
fatalError("Only available on WebAssembly")
}
#endif
@inline(never) fileprivate func _bjs_SwiftBox_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 {
return _bjs_SwiftBox_wrap_extern(pointer)
}

extension SwiftBox: BridgedSwiftGenericBridgeable {
@_spi(BridgeJS) public static let bridgeJSTypeHandle = SwiftBox.bridgeJSMakeTypeHandle()
}

extension SwiftChoice: BridgedSwiftGenericBridgeable {
@_spi(BridgeJS) public static let bridgeJSTypeHandle = SwiftChoice.bridgeJSMakeTypeHandle()
}

#if arch(wasm32)
@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles")
fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer<Int32>?, _ count: Int32)

@_expose(wasm, "bjs_TestModule_register_type_handles")
public func _bjs_TestModule_register_type_handles() {
let typeIds: [Int32] = [
SwiftBox.bridgeJSTypeID,
SwiftChoice.bridgeJSTypeID,
]
typeIds.withUnsafeBufferPointer { buffer in
_bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count))
}
}
#endif
Loading
Loading