Skip to content

Commit 853d8ce

Browse files
committed
BridgeJS: Tests for same-named types across modules
- Link two skeletons that both export a `Point` struct and a `run` function with identical Swift names but different public JS names, and assert the generated glue keeps module-qualified struct hooks, helper keys, and thunk names distinct while the TS surface stays flat (snapshot: SameNamedTypesAcrossModules). - Assert that two modules exporting the same public name (type or function) at the same namespace path fail to link with the new duplicate-export diagnostic.
1 parent 9419743 commit 853d8ce

3 files changed

Lines changed: 504 additions & 0 deletions

File tree

Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,127 @@ import Testing
126126
try snapshot(bridgeJSLink: bridgeJSLink, name: "MixedModules")
127127
}
128128

129+
private func buildSkeleton(
130+
moduleName: String,
131+
source: String,
132+
inputFilePath: String = "input.swift"
133+
) throws -> BridgeJSSkeleton {
134+
let swiftAPI = SwiftToSkeleton(
135+
progress: .silent,
136+
moduleName: moduleName,
137+
exposeToGlobal: false,
138+
externalModuleIndex: .empty
139+
)
140+
swiftAPI.addSourceFile(Parser.parse(source: source), inputFilePath: inputFilePath)
141+
return try swiftAPI.finalize()
142+
}
143+
144+
@Test
145+
func sameNamedTypesAcrossModulesLinkWithDistinctABINames() throws {
146+
// Both modules export a `Point` struct and a `run` function with identical Swift
147+
// names but different public JS names (one is namespaced). The module-qualified
148+
// ABI names must keep the generated glue (struct hooks, helper keys, and
149+
// function thunk names) distinct, while the public JS/TS surface stays flat.
150+
let moduleA = try buildSkeleton(
151+
moduleName: "ModuleA",
152+
source: """
153+
@JS public struct Point {
154+
public let x: Double
155+
public let y: Double
156+
@JS public init(x: Double, y: Double) {
157+
self.x = x
158+
self.y = y
159+
}
160+
}
161+
@JS public enum Validation {
162+
case valid
163+
case invalid(reason: String)
164+
}
165+
@JS public func run(point: Point, validation: Validation) -> Point {
166+
point
167+
}
168+
"""
169+
)
170+
let moduleB = try buildSkeleton(
171+
moduleName: "ModuleB",
172+
source: """
173+
@JS(namespace: "Inner") public struct Point {
174+
public let x: Double
175+
public let y: Double
176+
@JS public init(x: Double, y: Double) {
177+
self.x = x
178+
self.y = y
179+
}
180+
}
181+
@JS(namespace: "Inner") public func run() {
182+
}
183+
"""
184+
)
185+
let bridgeJSLink = BridgeJSLink(skeletons: [moduleA, moduleB], sharedMemory: false)
186+
187+
let (outputJs, outputDts) = try bridgeJSLink.link()
188+
189+
// Struct hooks and helper keys are module-qualified and distinct.
190+
#expect(outputJs.contains("swift_js_struct_lower_ModuleA_Point"))
191+
#expect(outputJs.contains("swift_js_struct_lower_ModuleB_Point"))
192+
#expect(outputJs.contains("structHelpers.ModuleA_Point"))
193+
#expect(outputJs.contains("structHelpers.ModuleB_Point"))
194+
// Associated-value enum helper keys are module-qualified.
195+
#expect(outputJs.contains("enumHelpers.ModuleA_Validation"))
196+
// Exported function thunk names are module-qualified and distinct.
197+
#expect(outputJs.contains("instance.exports.bjs_ModuleA_run"))
198+
#expect(outputJs.contains("instance.exports.bjs_ModuleB_Inner_run"))
199+
// The public TS surface stays flat (no module prefixes).
200+
#expect(!outputDts.contains("ModuleA"))
201+
#expect(!outputDts.contains("ModuleB"))
202+
203+
try snapshot(bridgeJSLink: bridgeJSLink, name: "SameNamedTypesAcrossModules")
204+
}
205+
206+
@Test
207+
func duplicatePublicNamesAcrossModulesProduceDiagnostic() throws {
208+
let source = """
209+
@JS public struct Point {
210+
public let x: Double
211+
@JS public init(x: Double) {
212+
self.x = x
213+
}
214+
}
215+
"""
216+
let moduleA = try buildSkeleton(moduleName: "ModuleA", source: source)
217+
let moduleB = try buildSkeleton(moduleName: "ModuleB", source: source)
218+
let bridgeJSLink = BridgeJSLink(skeletons: [moduleA, moduleB], sharedMemory: false)
219+
220+
do {
221+
_ = try bridgeJSLink.link()
222+
Issue.record("Expected duplicate public export name diagnostic, but linking succeeded")
223+
} catch let error as BridgeJSLinkError {
224+
#expect(error.message.contains("Duplicate exported name 'Point'"))
225+
#expect(error.message.contains("ModuleA"))
226+
#expect(error.message.contains("ModuleB"))
227+
}
228+
}
229+
230+
@Test
231+
func duplicatePublicFunctionNamesAcrossModulesProduceDiagnostic() throws {
232+
let moduleA = try buildSkeleton(
233+
moduleName: "ModuleA",
234+
source: "@JS public func run() {}"
235+
)
236+
let moduleB = try buildSkeleton(
237+
moduleName: "ModuleB",
238+
source: "@JS public func run() {}"
239+
)
240+
let bridgeJSLink = BridgeJSLink(skeletons: [moduleA, moduleB], sharedMemory: false)
241+
242+
do {
243+
_ = try bridgeJSLink.link()
244+
Issue.record("Expected duplicate public export name diagnostic, but linking succeeded")
245+
} catch let error as BridgeJSLinkError {
246+
#expect(error.message.contains("Duplicate exported name 'run'"))
247+
}
248+
}
249+
129250
@Test
130251
func perClassIdentityModeFromAnnotation() throws {
131252
let url = Self.inputsDirectory.appendingPathComponent("IdentityModeClass.swift")
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
2+
// DO NOT EDIT.
3+
//
4+
// To update this file, just rebuild your project or run
5+
// `swift package bridge-js`.
6+
7+
export const ValidationValues: {
8+
readonly Tag: {
9+
readonly Valid: 0;
10+
readonly Invalid: 1;
11+
};
12+
};
13+
14+
export type ValidationTag =
15+
{ tag: typeof ValidationValues.Tag.Valid } | { tag: typeof ValidationValues.Tag.Invalid; reason: string }
16+
17+
export interface Point {
18+
x: number;
19+
y: number;
20+
}
21+
export type ValidationObject = typeof ValidationValues;
22+
23+
export namespace Inner {
24+
export interface Point {
25+
x: number;
26+
y: number;
27+
}
28+
}
29+
export type Exports = {
30+
run(point: Point, validation: ValidationTag): Point;
31+
Validation: ValidationObject
32+
Inner: {
33+
run(): void;
34+
Point: {
35+
init(x: number, y: number): Point;
36+
},
37+
},
38+
Point: {
39+
init(x: number, y: number): Point;
40+
},
41+
}
42+
export type Imports = {
43+
}
44+
export function createInstantiator(options: {
45+
imports: Imports;
46+
}, swift: any): Promise<{
47+
addImports: (importObject: WebAssembly.Imports) => void;
48+
setInstance: (instance: WebAssembly.Instance) => void;
49+
createExports: (instance: WebAssembly.Instance) => Exports;
50+
}>;

0 commit comments

Comments
 (0)