Skip to content

Commit eca0298

Browse files
authored
Merge pull request #504 from GoodNotes/katei/7b73-generate-jsbridg
BridgeJS: Generate `_JSBridgedClass` conformance via macro
2 parents 4c0d367 + 58f2f1b commit eca0298

File tree

12 files changed

+107
-27
lines changed

12 files changed

+107
-27
lines changed

Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.Macros.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88

99
@JSFunction func createTS2Swift() throws (JSException) -> TS2Swift
1010

11-
@JSClass struct TS2Swift: _JSBridgedClass {
11+
@JSClass struct TS2Swift {
1212
@JSFunction func convert(_ ts: String) throws (JSException) -> String
1313
}

Plugins/BridgeJS/Sources/BridgeJSMacros/JSClassMacro.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ extension JSClassMacro: MemberMacro {
99
public static func expansion(
1010
of node: AttributeSyntax,
1111
providingMembersOf declaration: some DeclGroupSyntax,
12+
conformingTo protocols: [TypeSyntax],
1213
in context: some MacroExpansionContext
1314
) throws -> [DeclSyntax] {
1415
var members: [DeclSyntax] = []
@@ -49,3 +50,20 @@ extension JSClassMacro: MemberMacro {
4950
return members
5051
}
5152
}
53+
54+
extension JSClassMacro: ExtensionMacro {
55+
public static func expansion(
56+
of node: AttributeSyntax,
57+
attachedTo declaration: some DeclGroupSyntax,
58+
providingExtensionsOf type: some TypeSyntaxProtocol,
59+
conformingTo protocols: [TypeSyntax],
60+
in context: some MacroExpansionContext
61+
) throws -> [ExtensionDeclSyntax] {
62+
guard !protocols.isEmpty else { return [] }
63+
64+
let conformanceList = protocols.map { $0.trimmed.description }.joined(separator: ", ")
65+
return [
66+
try ExtensionDeclSyntax("extension \(type.trimmed): \(raw: conformanceList) {}")
67+
]
68+
}
69+
}

Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ export class TypeProcessor {
213213
if (!node.name) return;
214214

215215
const className = this.renderIdentifier(node.name.text);
216-
this.swiftLines.push(`@JSClass struct ${className}: _JSBridgedClass {`);
216+
this.swiftLines.push(`@JSClass struct ${className} {`);
217217

218218
// Process members in declaration order
219219
for (const member of node.members) {
@@ -268,7 +268,7 @@ export class TypeProcessor {
268268
*/
269269
visitStructuredType(name, members) {
270270
const typeName = this.renderIdentifier(name);
271-
this.swiftLines.push(`@JSClass struct ${typeName}: _JSBridgedClass {`);
271+
this.swiftLines.push(`@JSClass struct ${typeName} {`);
272272

273273
// Collect all declarations with their positions to preserve order
274274
/** @type {Array<{ decl: ts.Node, symbol: ts.Symbol, position: number }>} */

Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSClassMacroTests.swift

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import SwiftDiagnostics
22
import SwiftSyntax
3+
import SwiftSyntaxMacroExpansion
34
import SwiftSyntaxMacros
45
import SwiftSyntaxMacrosTestSupport
56
import Testing
67
import BridgeJSMacros
78

89
@Suite struct JSClassMacroTests {
910
private let indentationWidth: Trivia = .spaces(4)
11+
private let macroSpecs: [String: MacroSpec] = [
12+
"JSClass": MacroSpec(type: JSClassMacro.self, conformances: ["_JSBridgedClass"])
13+
]
1014

1115
@Test func emptyStruct() {
1216
assertMacroExpansion(
@@ -23,8 +27,11 @@ import BridgeJSMacros
2327
self.jsObject = jsObject
2428
}
2529
}
30+
31+
extension MyClass: _JSBridgedClass {
32+
}
2633
""",
27-
macros: ["JSClass": JSClassMacro.self],
34+
macroSpecs: macroSpecs,
2835
indentationWidth: indentationWidth
2936
)
3037
}
@@ -45,8 +52,11 @@ import BridgeJSMacros
4552
self.jsObject = jsObject
4653
}
4754
}
55+
56+
extension MyClass: _JSBridgedClass {
57+
}
4858
""",
49-
macros: ["JSClass": JSClassMacro.self],
59+
macroSpecs: macroSpecs,
5060
indentationWidth: indentationWidth
5161
)
5262
}
@@ -69,8 +79,11 @@ import BridgeJSMacros
6979
self.jsObject = jsObject
7080
}
7181
}
82+
83+
extension MyClass: _JSBridgedClass {
84+
}
7285
""",
73-
macros: ["JSClass": JSClassMacro.self],
86+
macroSpecs: macroSpecs,
7487
indentationWidth: indentationWidth
7588
)
7689
}
@@ -95,8 +108,11 @@ import BridgeJSMacros
95108
self.jsObject = jsObject
96109
}
97110
}
111+
112+
extension MyClass: _JSBridgedClass {
113+
}
98114
""",
99-
macros: ["JSClass": JSClassMacro.self],
115+
macroSpecs: macroSpecs,
100116
indentationWidth: indentationWidth
101117
)
102118
}
@@ -119,8 +135,11 @@ import BridgeJSMacros
119135
self.jsObject = jsObject
120136
}
121137
}
138+
139+
extension MyClass: _JSBridgedClass {
140+
}
122141
""",
123-
macros: ["JSClass": JSClassMacro.self],
142+
macroSpecs: macroSpecs,
124143
indentationWidth: indentationWidth
125144
)
126145
}
@@ -140,8 +159,11 @@ import BridgeJSMacros
140159
self.jsObject = jsObject
141160
}
142161
}
162+
163+
extension MyClass: _JSBridgedClass {
164+
}
143165
""",
144-
macros: ["JSClass": JSClassMacro.self],
166+
macroSpecs: macroSpecs,
145167
indentationWidth: indentationWidth
146168
)
147169
}
@@ -161,8 +183,11 @@ import BridgeJSMacros
161183
self.jsObject = jsObject
162184
}
163185
}
186+
187+
extension MyEnum: _JSBridgedClass {
188+
}
164189
""",
165-
macros: ["JSClass": JSClassMacro.self],
190+
macroSpecs: macroSpecs,
166191
indentationWidth: indentationWidth
167192
)
168193
}
@@ -182,8 +207,11 @@ import BridgeJSMacros
182207
self.jsObject = jsObject
183208
}
184209
}
210+
211+
extension MyActor: _JSBridgedClass {
212+
}
185213
""",
186-
macros: ["JSClass": JSClassMacro.self],
214+
macroSpecs: macroSpecs,
187215
indentationWidth: indentationWidth
188216
)
189217
}
@@ -206,8 +234,11 @@ import BridgeJSMacros
206234
self.jsObject = jsObject
207235
}
208236
}
237+
238+
extension MyClass: _JSBridgedClass {
239+
}
209240
""",
210-
macros: ["JSClass": JSClassMacro.self],
241+
macroSpecs: macroSpecs,
211242
indentationWidth: indentationWidth
212243
)
213244
}
@@ -232,8 +263,11 @@ import BridgeJSMacros
232263
self.jsObject = jsObject
233264
}
234265
}
266+
267+
extension MyClass: _JSBridgedClass {
268+
}
235269
""",
236-
macros: ["JSClass": JSClassMacro.self],
270+
macroSpecs: macroSpecs,
237271
indentationWidth: indentationWidth
238272
)
239273
}
@@ -258,8 +292,11 @@ import BridgeJSMacros
258292
self.jsObject = jsObject
259293
}
260294
}
295+
296+
extension MyClass: _JSBridgedClass {
297+
}
261298
""",
262-
macros: ["JSClass": JSClassMacro.self],
299+
macroSpecs: macroSpecs,
263300
indentationWidth: indentationWidth
264301
)
265302
}
@@ -281,8 +318,32 @@ import BridgeJSMacros
281318
self.jsObject = jsObject
282319
}
283320
}
321+
322+
extension MyClass: _JSBridgedClass {
323+
}
324+
""",
325+
macroSpecs: macroSpecs,
326+
indentationWidth: indentationWidth
327+
)
328+
}
329+
330+
@Test func structAlreadyConforms() {
331+
assertMacroExpansion(
332+
"""
333+
@JSClass
334+
struct MyClass: _JSBridgedClass {
335+
}
336+
""",
337+
expandedSource: """
338+
struct MyClass: _JSBridgedClass {
339+
let jsObject: JSObject
340+
341+
init(unsafelyWrapping jsObject: JSObject) {
342+
self.jsObject = jsObject
343+
}
344+
}
284345
""",
285-
macros: ["JSClass": JSClassMacro.self],
346+
macroSpecs: macroSpecs,
286347
indentationWidth: indentationWidth
287348
)
288349
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.Macros.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
@JSFunction func returnAnimatable() throws (JSException) -> Animatable
1010

11-
@JSClass struct Animatable: _JSBridgedClass {
11+
@JSClass struct Animatable {
1212
@JSFunction func animate(_ keyframes: JSObject, _ options: JSObject) throws (JSException) -> JSObject
1313
@JSFunction func getAnimations(_ options: JSObject) throws (JSException) -> JSObject
1414
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.Macros.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88

99
@JSFunction func createArrayBuffer() throws (JSException) -> ArrayBufferLike
1010

11-
@JSClass struct ArrayBufferLike: _JSBridgedClass {
11+
@JSClass struct ArrayBufferLike {
1212
@JSGetter var byteLength: Double
1313
@JSFunction func slice(_ begin: Double, _ end: Double) throws (JSException) -> ArrayBufferLike
1414
}
1515

1616
@JSFunction func createWeirdObject() throws (JSException) -> WeirdNaming
1717

18-
@JSClass struct WeirdNaming: _JSBridgedClass {
18+
@JSClass struct WeirdNaming {
1919
@JSGetter var normalProperty: String
2020
@JSSetter func setNormalProperty(_ value: String) throws (JSException)
2121
@JSGetter var `for`: String

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.Macros.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
@JSFunction func createDatabaseConnection(_ config: JSObject) throws (JSException) -> DatabaseConnection
1010

11-
@JSClass struct DatabaseConnection: _JSBridgedClass {
11+
@JSClass struct DatabaseConnection {
1212
@JSFunction func connect(_ url: String) throws (JSException) -> Void
1313
@JSFunction func execute(_ query: String) throws (JSException) -> JSObject
1414
@JSGetter var isConnected: Bool
@@ -18,15 +18,15 @@
1818

1919
@JSFunction func createLogger(_ level: String) throws (JSException) -> Logger
2020

21-
@JSClass struct Logger: _JSBridgedClass {
21+
@JSClass struct Logger {
2222
@JSFunction func log(_ message: String) throws (JSException) -> Void
2323
@JSFunction func error(_ message: String, _ error: JSObject) throws (JSException) -> Void
2424
@JSGetter var level: String
2525
}
2626

2727
@JSFunction func getConfigManager() throws (JSException) -> ConfigManager
2828

29-
@JSClass struct ConfigManager: _JSBridgedClass {
29+
@JSClass struct ConfigManager {
3030
@JSFunction func get(_ key: String) throws (JSException) -> JSObject
3131
@JSFunction func set(_ key: String, _ value: JSObject) throws (JSException) -> Void
3232
@JSGetter var configPath: String

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.Macros.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88

99
@JSFunction func createTS2Skeleton() throws (JSException) -> TypeScriptProcessor
1010

11-
@JSClass struct TypeScriptProcessor: _JSBridgedClass {
11+
@JSClass struct TypeScriptProcessor {
1212
@JSFunction func convert(_ ts: String) throws (JSException) -> String
1313
@JSFunction func validate(_ ts: String) throws (JSException) -> Bool
1414
@JSGetter var version: String
1515
}
1616

1717
@JSFunction func createCodeGenerator(_ format: String) throws (JSException) -> CodeGenerator
1818

19-
@JSClass struct CodeGenerator: _JSBridgedClass {
19+
@JSClass struct CodeGenerator {
2020
@JSFunction func generate(_ input: JSObject) throws (JSException) -> String
2121
@JSGetter var outputFormat: String
2222
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.Macros.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
@_spi(Experimental) import JavaScriptKit
88

9-
@JSClass struct Greeter: _JSBridgedClass {
9+
@JSClass struct Greeter {
1010
@JSGetter var name: String
1111
@JSSetter func setName(_ value: String) throws (JSException)
1212
@JSGetter var age: Double

Sources/JavaScriptKit/JSBridgedType.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ extension JSBridgedType {
1515

1616
/// A protocol that Swift classes that are exposed to JavaScript via `@JS class` conform to.
1717
///
18-
/// The conformance is automatically synthesized by the BridgeJS code generator.
18+
/// The conformance is automatically synthesized by `@JSClass` for BridgeJS-generated declarations.
1919
public protocol _JSBridgedClass {
2020
/// The JavaScript object wrapped by this instance.
2121
/// You may assume that `jsObject instanceof Self.constructor == true`

0 commit comments

Comments
 (0)