Skip to content

Commit c58ffa4

Browse files
committed
BridgeJS: Add tests and fixtures for generic imports
1 parent ccd828b commit c58ffa4

108 files changed

Lines changed: 5315 additions & 94 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSCodegenTests.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ import Testing
141141
swiftParts.append(s)
142142
}
143143
}
144+
if let typeRegistration = GenericTypeRegistrationCodegen().render(for: skeleton) {
145+
swiftParts.append(typeRegistration)
146+
}
144147
let combinedSwift =
145148
swiftParts
146149
.map { $0.trimmingCharacters(in: .newlines) }

Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,26 @@ import Testing
139139
try snapshot(bridgeJSLink: bridgeJSLink, name: "MixedModules")
140140
}
141141

142+
private func linkedJS(forFixture input: String) throws -> String {
143+
let url = Self.inputsDirectory.appendingPathComponent(input)
144+
let name = url.deletingPathExtension().lastPathComponent
145+
let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8))
146+
let importSwift = SwiftToSkeleton(
147+
progress: .silent,
148+
moduleName: "TestModule",
149+
exposeToGlobal: false,
150+
externalModuleIndex: .empty
151+
)
152+
importSwift.addSourceFile(sourceFile, inputFilePath: "\(name).swift")
153+
let importResult = try importSwift.finalize()
154+
var bridgeJSLink = BridgeJSLink(sharedMemory: false)
155+
let encoder = JSONEncoder()
156+
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
157+
let unifiedData = try encoder.encode(importResult)
158+
try bridgeJSLink.addSkeletonFile(data: unifiedData)
159+
return try bridgeJSLink.link().0
160+
}
161+
142162
@Test
143163
func perClassIdentityModeFromAnnotation() throws {
144164
let url = Self.inputsDirectory.appendingPathComponent("IdentityModeClass.swift")
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import Foundation
2+
import SwiftParser
3+
import SwiftSyntax
4+
import Testing
5+
6+
@testable import BridgeJSLink
7+
@testable import BridgeJSCore
8+
@testable import BridgeJSSkeleton
9+
10+
func makeSkeleton(
11+
_ source: String,
12+
moduleName: String = "TestModule",
13+
dependencies: [(moduleName: String, skeleton: BridgeJSSkeleton)] = []
14+
) throws -> BridgeJSSkeleton {
15+
let swiftAPI = SwiftToSkeleton(
16+
progress: .silent,
17+
moduleName: moduleName,
18+
exposeToGlobal: false,
19+
externalModuleIndex: ExternalModuleIndex(dependencies: dependencies)
20+
)
21+
swiftAPI.addSourceFile(Parser.parse(source: source), inputFilePath: "\(moduleName).swift")
22+
return try swiftAPI.finalize()
23+
}
24+
25+
func expectDiagnostic(
26+
source: String,
27+
moduleName: String = "App",
28+
contains message: String,
29+
sourceLocation: Testing.SourceLocation = #_sourceLocation
30+
) {
31+
do {
32+
_ = try makeSkeleton(source, moduleName: moduleName)
33+
Issue.record("Expected diagnostic but resolution succeeded", sourceLocation: sourceLocation)
34+
} catch let error as BridgeJSCoreDiagnosticError {
35+
let combined = error.diagnostics.map(\.diagnostic.message).joined(separator: "\n")
36+
#expect(combined.contains(message), sourceLocation: sourceLocation)
37+
} catch {
38+
Issue.record("Unexpected error: \(error)", sourceLocation: sourceLocation)
39+
}
40+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import Testing
2+
3+
@Suite struct GenericExportDiagnosticsTests {
4+
5+
@Test
6+
func genericExportedFunctionRejected() {
7+
expectDiagnostic(
8+
source: """
9+
@JS public func identity<T: BridgedSwiftGenericBridgeable>(_ value: T) -> T { value }
10+
""",
11+
contains: "Generic parameters on exported @JS functions are not supported yet"
12+
)
13+
}
14+
15+
@Test
16+
func genericMethodOnExportedClassRejected() {
17+
expectDiagnostic(
18+
source: """
19+
@JS final class Box {
20+
@JS init() {}
21+
@JS func wrap<T: BridgedSwiftGenericBridgeable>(_ value: T) -> T { value }
22+
}
23+
""",
24+
contains: "Generic parameters on exported @JS functions are not supported yet"
25+
)
26+
}
27+
28+
@Test
29+
func genericMethodOnExportedStructRejected() {
30+
expectDiagnostic(
31+
source: """
32+
@JS struct Pair {
33+
@JS init() {}
34+
@JS func first<T: BridgedSwiftGenericBridgeable>(_ value: T) -> T { value }
35+
}
36+
""",
37+
contains: "Generic parameters on exported @JS functions are not supported yet"
38+
)
39+
}
40+
41+
@Test
42+
func genericStaticMethodOnExportedEnumRejected() {
43+
expectDiagnostic(
44+
source: """
45+
@JS enum Factory {
46+
case primary
47+
@JS static func one<T: BridgedSwiftGenericBridgeable>(_ value: T) -> T { value }
48+
}
49+
""",
50+
contains: "Generic parameters on exported @JS functions are not supported yet"
51+
)
52+
}
53+
54+
@Test
55+
func unconstrainedGenericExportedFunctionRejected() {
56+
expectDiagnostic(
57+
source: """
58+
@JS public func identity<T>(_ value: T) -> T { value }
59+
""",
60+
contains: "Generic parameters on exported @JS functions are not supported yet"
61+
)
62+
}
63+
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
import Testing
2+
3+
@testable import BridgeJSSkeleton
4+
5+
@Suite struct GenericImportDiagnosticsTests {
6+
7+
@Test
8+
func genericParameterRequiresBridgeableConstraint() {
9+
expectDiagnostic(
10+
source: """
11+
@JSFunction func identity<T>(_ value: T) throws(JSException) -> T
12+
""",
13+
contains: "Generic parameter 'T' must be constrained to 'BridgedSwiftGenericBridgeable'"
14+
)
15+
}
16+
17+
@Test
18+
func genericWhereClauseUnsupported() {
19+
expectDiagnostic(
20+
source: """
21+
@JSFunction func identity<T: BridgedSwiftGenericBridgeable>(_ value: T) throws(JSException) -> T where T: Sendable
22+
""",
23+
contains: "'where' clauses are not supported on @JSFunction"
24+
)
25+
}
26+
27+
@Test
28+
func asyncGenericImportUnsupported() {
29+
expectDiagnostic(
30+
source: """
31+
@JSFunction func identityAsync<T: BridgedSwiftGenericBridgeable>(_ value: T) async throws(JSException) -> T
32+
""",
33+
contains: "Generic @JSFunction declarations cannot be 'async' yet."
34+
)
35+
}
36+
37+
@Test
38+
func genericImportedMethodIsParsed() throws {
39+
let skeleton = try makeSkeleton(
40+
"""
41+
@JSClass struct Box {
42+
@JSFunction func member<T: BridgedSwiftGenericBridgeable>(_ value: T) throws(JSException) -> T
43+
}
44+
""",
45+
moduleName: "App"
46+
)
47+
let imported = try #require(skeleton.imported)
48+
let types = imported.children.flatMap { $0.types }
49+
let box = try #require(types.first { $0.name == "Box" })
50+
let method = try #require(box.methods.first { $0.name == "member" })
51+
#expect(method.genericParameters == ["T"])
52+
}
53+
54+
@Test
55+
func genericImportedConstructorIsParsed() throws {
56+
let skeleton = try makeSkeleton(
57+
"""
58+
@JSClass struct Box {
59+
@JSFunction init<T: BridgedSwiftGenericBridgeable>(_ value: T) throws(JSException)
60+
}
61+
""",
62+
moduleName: "App"
63+
)
64+
let imported = try #require(skeleton.imported)
65+
let types = imported.children.flatMap { $0.types }
66+
let box = try #require(types.first { $0.name == "Box" })
67+
let constructor = try #require(box.constructor)
68+
#expect(constructor.genericParameters == ["T"])
69+
#expect(constructor.parameters.map(\.type) == [.generic("T")])
70+
}
71+
72+
@Test
73+
func genericImportedConstructorUnconstrainedParamIsRejected() {
74+
expectDiagnostic(
75+
source: """
76+
@JSClass struct Box {
77+
@JSFunction init<T>(_ value: T) throws(JSException)
78+
}
79+
""",
80+
contains:
81+
"Generic parameter 'T' must be constrained to 'BridgedSwiftGenericBridgeable' to be used with @JSFunction."
82+
)
83+
}
84+
85+
@Test
86+
func genericImportedConstructorUnusedTypeParamIsRejected() {
87+
expectDiagnostic(
88+
source: """
89+
@JSClass struct Box {
90+
@JSFunction init<T: BridgedSwiftGenericBridgeable>(_ value: Int) throws(JSException)
91+
}
92+
""",
93+
contains:
94+
"The generic parameter 'T' must be used in a parameter of a generic @JSFunction initializer."
95+
)
96+
}
97+
98+
@Test
99+
func genericImportedConstructorAsyncIsRejected() {
100+
expectDiagnostic(
101+
source: """
102+
@JSClass struct Box {
103+
@JSFunction init<T: BridgedSwiftGenericBridgeable>(_ value: T) async throws(JSException)
104+
}
105+
""",
106+
contains: "Generic @JSFunction declarations cannot be 'async' yet."
107+
)
108+
}
109+
110+
@Test
111+
func genericImportedConstructorUnsupportedWrapperFormIsRejected() {
112+
expectDiagnostic(
113+
source: """
114+
@JSClass struct Box {
115+
@JSFunction init<T: BridgedSwiftGenericBridgeable>(_ value: [[T]]) throws(JSException)
116+
}
117+
""",
118+
contains: "may only be used as a bare type"
119+
)
120+
}
121+
122+
@Test(arguments: [
123+
("[[T]]", "@JSFunction func f<T: BridgedSwiftGenericBridgeable>(_ v: [[T]]) throws(JSException)"),
124+
("[T?]", "@JSFunction func f<T: BridgedSwiftGenericBridgeable>(_ v: [T?]) throws(JSException)"),
125+
("T??", "@JSFunction func f<T: BridgedSwiftGenericBridgeable>(_ v: T??) throws(JSException)"),
126+
("[Int: T]", "@JSFunction func f<T: BridgedSwiftGenericBridgeable>(_ v: [Int: T]) throws(JSException)"),
127+
])
128+
func unsupportedGenericWrapperFormsInParameter(label: String, source: String) {
129+
expectDiagnostic(
130+
source: source,
131+
contains: "may only be used as a bare type"
132+
)
133+
}
134+
135+
@Test(arguments: [
136+
("[[T]]", "@JSFunction func f<T: BridgedSwiftGenericBridgeable>(_ v: T) throws(JSException) -> [[T]]"),
137+
("[T?]", "@JSFunction func f<T: BridgedSwiftGenericBridgeable>(_ v: T) throws(JSException) -> [T?]"),
138+
("T??", "@JSFunction func f<T: BridgedSwiftGenericBridgeable>(_ v: T) throws(JSException) -> T??"),
139+
("[Int: T]", "@JSFunction func f<T: BridgedSwiftGenericBridgeable>(_ v: T) throws(JSException) -> [Int: T]"),
140+
])
141+
func unsupportedGenericWrapperFormsInReturn(label: String, source: String) {
142+
expectDiagnostic(
143+
source: source,
144+
contains: "may only be used as a bare type"
145+
)
146+
}
147+
148+
@Test
149+
func genericImportedMethodAsyncIsRejected() {
150+
expectDiagnostic(
151+
source: """
152+
@JSClass struct Box {
153+
@JSFunction func member<T: BridgedSwiftGenericBridgeable>(_ value: T) async throws(JSException) -> T
154+
}
155+
""",
156+
contains: "Generic @JSFunction declarations cannot be 'async' yet."
157+
)
158+
}
159+
160+
@Test
161+
func genericImportedMethodUnconstrainedParamIsRejected() {
162+
expectDiagnostic(
163+
source: """
164+
@JSClass struct Box {
165+
@JSFunction func member<T>(_ value: T) throws(JSException) -> T
166+
}
167+
""",
168+
contains:
169+
"Generic parameter 'T' must be constrained to 'BridgedSwiftGenericBridgeable' to be used with @JSFunction."
170+
)
171+
}
172+
173+
@Test
174+
func genericImportedFunctionUnusedTypeParamIsRejected() {
175+
expectDiagnostic(
176+
source: """
177+
@JSFunction func unused<T: BridgedSwiftGenericBridgeable>() throws(JSException) -> Int
178+
""",
179+
contains:
180+
"The generic parameter 'T' must be used in a parameter or return type of a generic @JSFunction declaration."
181+
)
182+
}
183+
184+
@Test
185+
func genericImportedMethodUnusedTypeParamIsRejected() {
186+
expectDiagnostic(
187+
source: """
188+
@JSClass struct Box {
189+
@JSFunction func member<T: BridgedSwiftGenericBridgeable>() throws(JSException) -> Int
190+
}
191+
""",
192+
contains:
193+
"The generic parameter 'T' must be used in a parameter or return type of a generic @JSFunction declaration."
194+
)
195+
}
196+
197+
@Test
198+
func genericImportedReturnOnlyTypeParamIsAllowed() throws {
199+
let skeleton = try makeSkeleton(
200+
"""
201+
@JSFunction func make<T: BridgedSwiftGenericBridgeable>() throws(JSException) -> T
202+
""",
203+
moduleName: "App"
204+
)
205+
let imported = try #require(skeleton.imported)
206+
let functions = imported.children.flatMap { $0.functions }
207+
let function = try #require(functions.first { $0.name == "make" })
208+
#expect(function.genericParameters == ["T"])
209+
}
210+
}

0 commit comments

Comments
 (0)