Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@

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

@JSClass struct TS2Swift: _JSBridgedClass {
@JSClass struct TS2Swift {
@JSFunction func convert(_ ts: String) throws (JSException) -> String
}
18 changes: 18 additions & 0 deletions Plugins/BridgeJS/Sources/BridgeJSMacros/JSClassMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ extension JSClassMacro: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
var members: [DeclSyntax] = []
Expand Down Expand Up @@ -49,3 +50,20 @@ extension JSClassMacro: MemberMacro {
return members
}
}

extension JSClassMacro: ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,
attachedTo declaration: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [ExtensionDeclSyntax] {
guard !protocols.isEmpty else { return [] }

let conformanceList = protocols.map { $0.trimmed.description }.joined(separator: ", ")
return [
try ExtensionDeclSyntax("extension \(type.trimmed): \(raw: conformanceList) {}")
]
}
}
4 changes: 2 additions & 2 deletions Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export class TypeProcessor {
if (!node.name) return;

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

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

// Collect all declarations with their positions to preserve order
/** @type {Array<{ decl: ts.Node, symbol: ts.Symbol, position: number }>} */
Expand Down
85 changes: 73 additions & 12 deletions Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSClassMacroTests.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import SwiftDiagnostics
import SwiftSyntax
import SwiftSyntaxMacroExpansion
import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
import Testing
import BridgeJSMacros

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

@Test func emptyStruct() {
assertMacroExpansion(
Expand All @@ -23,8 +27,11 @@ import BridgeJSMacros
self.jsObject = jsObject
}
}

extension MyClass: _JSBridgedClass {
}
""",
macros: ["JSClass": JSClassMacro.self],
macroSpecs: macroSpecs,
indentationWidth: indentationWidth
)
}
Expand All @@ -45,8 +52,11 @@ import BridgeJSMacros
self.jsObject = jsObject
}
}

extension MyClass: _JSBridgedClass {
}
""",
macros: ["JSClass": JSClassMacro.self],
macroSpecs: macroSpecs,
indentationWidth: indentationWidth
)
}
Expand All @@ -69,8 +79,11 @@ import BridgeJSMacros
self.jsObject = jsObject
}
}

extension MyClass: _JSBridgedClass {
}
""",
macros: ["JSClass": JSClassMacro.self],
macroSpecs: macroSpecs,
indentationWidth: indentationWidth
)
}
Expand All @@ -95,8 +108,11 @@ import BridgeJSMacros
self.jsObject = jsObject
}
}

extension MyClass: _JSBridgedClass {
}
""",
macros: ["JSClass": JSClassMacro.self],
macroSpecs: macroSpecs,
indentationWidth: indentationWidth
)
}
Expand All @@ -119,8 +135,11 @@ import BridgeJSMacros
self.jsObject = jsObject
}
}

extension MyClass: _JSBridgedClass {
}
""",
macros: ["JSClass": JSClassMacro.self],
macroSpecs: macroSpecs,
indentationWidth: indentationWidth
)
}
Expand All @@ -140,8 +159,11 @@ import BridgeJSMacros
self.jsObject = jsObject
}
}

extension MyClass: _JSBridgedClass {
}
""",
macros: ["JSClass": JSClassMacro.self],
macroSpecs: macroSpecs,
indentationWidth: indentationWidth
)
}
Expand All @@ -161,8 +183,11 @@ import BridgeJSMacros
self.jsObject = jsObject
}
}

extension MyEnum: _JSBridgedClass {
}
""",
macros: ["JSClass": JSClassMacro.self],
macroSpecs: macroSpecs,
indentationWidth: indentationWidth
)
}
Expand All @@ -182,8 +207,11 @@ import BridgeJSMacros
self.jsObject = jsObject
}
}

extension MyActor: _JSBridgedClass {
}
""",
macros: ["JSClass": JSClassMacro.self],
macroSpecs: macroSpecs,
indentationWidth: indentationWidth
)
}
Expand All @@ -206,8 +234,11 @@ import BridgeJSMacros
self.jsObject = jsObject
}
}

extension MyClass: _JSBridgedClass {
}
""",
macros: ["JSClass": JSClassMacro.self],
macroSpecs: macroSpecs,
indentationWidth: indentationWidth
)
}
Expand All @@ -232,8 +263,11 @@ import BridgeJSMacros
self.jsObject = jsObject
}
}

extension MyClass: _JSBridgedClass {
}
""",
macros: ["JSClass": JSClassMacro.self],
macroSpecs: macroSpecs,
indentationWidth: indentationWidth
)
}
Expand All @@ -258,8 +292,11 @@ import BridgeJSMacros
self.jsObject = jsObject
}
}

extension MyClass: _JSBridgedClass {
}
""",
macros: ["JSClass": JSClassMacro.self],
macroSpecs: macroSpecs,
indentationWidth: indentationWidth
)
}
Expand All @@ -281,8 +318,32 @@ import BridgeJSMacros
self.jsObject = jsObject
}
}

extension MyClass: _JSBridgedClass {
}
""",
macroSpecs: macroSpecs,
indentationWidth: indentationWidth
)
}

@Test func structAlreadyConforms() {
assertMacroExpansion(
"""
@JSClass
struct MyClass: _JSBridgedClass {
}
""",
expandedSource: """
struct MyClass: _JSBridgedClass {
let jsObject: JSObject

init(unsafelyWrapping jsObject: JSObject) {
self.jsObject = jsObject
}
}
""",
macros: ["JSClass": JSClassMacro.self],
macroSpecs: macroSpecs,
indentationWidth: indentationWidth
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

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

@JSClass struct Animatable: _JSBridgedClass {
@JSClass struct Animatable {
@JSFunction func animate(_ keyframes: JSObject, _ options: JSObject) throws (JSException) -> JSObject
@JSFunction func getAnimations(_ options: JSObject) throws (JSException) -> JSObject
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@

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

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

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

@JSClass struct WeirdNaming: _JSBridgedClass {
@JSClass struct WeirdNaming {
@JSGetter var normalProperty: String
@JSSetter func setNormalProperty(_ value: String) throws (JSException)
@JSGetter var `for`: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

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

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

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

@JSClass struct Logger: _JSBridgedClass {
@JSClass struct Logger {
@JSFunction func log(_ message: String) throws (JSException) -> Void
@JSFunction func error(_ message: String, _ error: JSObject) throws (JSException) -> Void
@JSGetter var level: String
}

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

@JSClass struct ConfigManager: _JSBridgedClass {
@JSClass struct ConfigManager {
@JSFunction func get(_ key: String) throws (JSException) -> JSObject
@JSFunction func set(_ key: String, _ value: JSObject) throws (JSException) -> Void
@JSGetter var configPath: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@

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

@JSClass struct TypeScriptProcessor: _JSBridgedClass {
@JSClass struct TypeScriptProcessor {
@JSFunction func convert(_ ts: String) throws (JSException) -> String
@JSFunction func validate(_ ts: String) throws (JSException) -> Bool
@JSGetter var version: String
}

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

@JSClass struct CodeGenerator: _JSBridgedClass {
@JSClass struct CodeGenerator {
@JSFunction func generate(_ input: JSObject) throws (JSException) -> String
@JSGetter var outputFormat: String
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

@_spi(Experimental) import JavaScriptKit

@JSClass struct Greeter: _JSBridgedClass {
@JSClass struct Greeter {
@JSGetter var name: String
@JSSetter func setName(_ value: String) throws (JSException)
@JSGetter var age: Double
Expand Down
2 changes: 1 addition & 1 deletion Sources/JavaScriptKit/JSBridgedType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ extension JSBridgedType {

/// A protocol that Swift classes that are exposed to JavaScript via `@JS class` conform to.
///
/// The conformance is automatically synthesized by the BridgeJS code generator.
/// The conformance is automatically synthesized by `@JSClass` for BridgeJS-generated declarations.
public protocol _JSBridgedClass {
/// The JavaScript object wrapped by this instance.
/// You may assume that `jsObject instanceof Self.constructor == true`
Expand Down
3 changes: 2 additions & 1 deletion Sources/JavaScriptKit/Macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,15 @@ public macro JSFunction() =
/// @_spi(Experimental) import JavaScriptKit
///
/// @JSClass
/// struct JsGreeter: _JSBridgedClass {
/// struct JsGreeter {
/// @JSGetter var name: String
/// @JSSetter func setName(_ value: String) throws (JSException)
/// @JSFunction init(_ name: String) throws (JSException)
/// @JSFunction func greet() throws (JSException) -> String
/// }
/// ```
@attached(member, names: arbitrary)
@attached(extension, conformances: _JSBridgedClass)
@_spi(Experimental)
public macro JSClass() =
#externalMacro(module: "BridgeJSMacros", type: "JSClassMacro")
2 changes: 1 addition & 1 deletion Tests/BridgeJSRuntimeTests/Generated/BridgeJS.Macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

@JSFunction func jsThrowOrString(_ shouldThrow: Bool) throws (JSException) -> String

@JSClass struct JsGreeter: _JSBridgedClass {
@JSClass struct JsGreeter {
@JSGetter var name: String
@JSSetter func setName(_ value: String) throws (JSException)
@JSGetter var `prefix`: String
Expand Down