Skip to content

Commit ba50c36

Browse files
WIP
1 parent d8ed5b6 commit ba50c36

36 files changed

+1254
-275
lines changed

Examples/PlayBridgeJS/Sources/Generated/JavaScript/BridgeJS.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@
166166
],
167167
"types" : [
168168
{
169+
"getters" : [
170+
171+
],
169172
"methods" : [
170173
{
171174
"name" : "convert",
@@ -187,7 +190,7 @@
187190
}
188191
],
189192
"name" : "TS2Swift",
190-
"properties" : [
193+
"setters" : [
191194

192195
]
193196
}

Plugins/BridgeJS/Sources/BridgeJSCore/ImportSwiftMacros.swift

Lines changed: 231 additions & 29 deletions
Large diffs are not rendered by default.

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -327,12 +327,12 @@ public struct ImportTS {
327327

328328
private static func thunkName(
329329
type: ImportedTypeSkeleton,
330-
property: ImportedPropertySkeleton,
330+
propertyName: String,
331331
operation: String
332332
)
333333
-> String
334334
{
335-
"_$\(type.name)_\(property.name)_\(operation)"
335+
"_$\(type.name)_\(propertyName)_\(operation)"
336336
}
337337

338338
func renderSwiftThunk(
@@ -395,52 +395,50 @@ public struct ImportTS {
395395
]
396396
}
397397

398-
func renderGetterDecl(property: ImportedPropertySkeleton) throws -> DeclSyntax {
398+
func renderGetterDecl(getter: ImportedGetterSkeleton) throws -> DeclSyntax {
399399
let builder = CallJSEmission(
400400
moduleName: moduleName,
401-
abiName: property.getterAbiName(context: type)
401+
abiName: getter.abiName(context: type)
402402
)
403403
try builder.lowerParameter(param: selfParameter)
404-
try builder.call(returnType: property.type)
405-
try builder.liftReturnValue(returnType: property.type)
404+
try builder.call(returnType: getter.type)
405+
try builder.liftReturnValue(returnType: getter.type)
406406
topLevelDecls.append(builder.renderImportDecl())
407407
return DeclSyntax(
408408
builder.renderThunkDecl(
409-
name: Self.thunkName(type: type, property: property, operation: "get"),
409+
name: Self.thunkName(type: type, propertyName: getter.name, operation: "get"),
410410
parameters: [selfParameter],
411-
returnType: property.type
411+
returnType: getter.type
412412
)
413413
)
414414
}
415415

416-
func renderSetterDecl(property: ImportedPropertySkeleton) throws -> DeclSyntax {
416+
func renderSetterDecl(setter: ImportedSetterSkeleton) throws -> DeclSyntax {
417417
let builder = CallJSEmission(
418418
moduleName: moduleName,
419-
abiName: property.setterAbiName(context: type)
419+
abiName: setter.abiName(context: type)
420420
)
421-
let newValue = Parameter(label: nil, name: "newValue", type: property.type)
421+
let newValue = Parameter(label: nil, name: "newValue", type: setter.type)
422422
try builder.lowerParameter(param: selfParameter)
423423
try builder.lowerParameter(param: newValue)
424424
try builder.call(returnType: .void)
425425
topLevelDecls.append(builder.renderImportDecl())
426426
return builder.renderThunkDecl(
427-
name: Self.thunkName(type: type, property: property, operation: "set"),
427+
name: Self.thunkName(type: type, propertyName: setter.name, operation: "set"),
428428
parameters: [selfParameter, newValue],
429429
returnType: .void
430430
)
431431
}
432-
433-
func renderPropertyDecl(property: ImportedPropertySkeleton) throws -> [DeclSyntax] {
434-
var decls: [DeclSyntax] = [try renderGetterDecl(property: property)]
435-
decls.append(try renderSetterDecl(property: property))
436-
return decls
437-
}
438432
if let constructor = type.constructor {
439433
decls.append(contentsOf: try renderConstructorDecl(constructor: constructor))
440434
}
441435

442-
for property in type.properties {
443-
decls.append(contentsOf: try renderPropertyDecl(property: property))
436+
for getter in type.getters {
437+
decls.append(try renderGetterDecl(getter: getter))
438+
}
439+
440+
for setter in type.setters {
441+
decls.append(try renderSetterDecl(setter: setter))
444442
}
445443

446444
for method in type.methods {

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,14 +1159,20 @@ struct BridgeJSLink {
11591159
printer.write(methodSignature)
11601160
}
11611161

1162-
// Add properties
1163-
for property in type.properties {
1164-
let propertySignature =
1165-
property.isReadonly
1166-
? "readonly \(property.name): \(resolveTypeScriptType(property.type));"
1167-
: "\(property.name): \(resolveTypeScriptType(property.type));"
1162+
// Add properties from getters
1163+
var propertyNames = Set<String>()
1164+
for getter in type.getters {
1165+
propertyNames.insert(getter.name)
1166+
let hasSetter = type.setters.contains { $0.name == getter.name }
1167+
let propertySignature = hasSetter
1168+
? "\(getter.name): \(resolveTypeScriptType(getter.type));"
1169+
: "readonly \(getter.name): \(resolveTypeScriptType(getter.type));"
11681170
printer.write(propertySignature)
11691171
}
1172+
// Add setters that don't have corresponding getters
1173+
for setter in type.setters where !propertyNames.contains(setter.name) {
1174+
printer.write("\(setter.name): \(resolveTypeScriptType(setter.type));")
1175+
}
11701176

11711177
printer.unindent()
11721178
printer.write("}")
@@ -2882,34 +2888,34 @@ extension BridgeJSLink {
28822888
constructor: constructor
28832889
)
28842890
}
2885-
for property in type.properties {
2886-
let getterAbiName = property.getterAbiName(context: type)
2887-
let (js, dts) = try renderImportedProperty(
2888-
property: property,
2891+
for getter in type.getters {
2892+
let getterAbiName = getter.abiName(context: type)
2893+
let (js, dts) = try renderImportedGetter(
2894+
getter: getter,
28892895
abiName: getterAbiName,
28902896
emitCall: { thunkBuilder in
2891-
return try thunkBuilder.callPropertyGetter(name: property.name, returnType: property.type)
2897+
return try thunkBuilder.callPropertyGetter(name: getter.name, returnType: getter.type)
28922898
}
28932899
)
28942900
importObjectBuilder.assignToImportObject(name: getterAbiName, function: js)
28952901
importObjectBuilder.appendDts(dts)
2896-
2897-
if !property.isReadonly {
2898-
let setterAbiName = property.setterAbiName(context: type)
2899-
let (js, dts) = try renderImportedProperty(
2900-
property: property,
2901-
abiName: setterAbiName,
2902-
emitCall: { thunkBuilder in
2903-
try thunkBuilder.liftParameter(
2904-
param: Parameter(label: nil, name: "newValue", type: property.type)
2905-
)
2906-
thunkBuilder.callPropertySetter(name: property.name, returnType: property.type)
2907-
return nil
2908-
}
2909-
)
2910-
importObjectBuilder.assignToImportObject(name: setterAbiName, function: js)
2911-
importObjectBuilder.appendDts(dts)
2912-
}
2902+
}
2903+
2904+
for setter in type.setters {
2905+
let setterAbiName = setter.abiName(context: type)
2906+
let (js, dts) = try renderImportedSetter(
2907+
setter: setter,
2908+
abiName: setterAbiName,
2909+
emitCall: { thunkBuilder in
2910+
try thunkBuilder.liftParameter(
2911+
param: Parameter(label: nil, name: "newValue", type: setter.type)
2912+
)
2913+
thunkBuilder.callPropertySetter(name: setter.name, returnType: setter.type)
2914+
return nil
2915+
}
2916+
)
2917+
importObjectBuilder.assignToImportObject(name: setterAbiName, function: js)
2918+
importObjectBuilder.appendDts(dts)
29132919
}
29142920
for method in type.methods {
29152921
let (js, dts) = try renderImportedMethod(context: type, method: method)
@@ -2949,8 +2955,8 @@ extension BridgeJSLink {
29492955
importObjectBuilder.appendDts(dtsPrinter.lines)
29502956
}
29512957

2952-
func renderImportedProperty(
2953-
property: ImportedPropertySkeleton,
2958+
func renderImportedGetter(
2959+
getter: ImportedGetterSkeleton,
29542960
abiName: String,
29552961
emitCall: (ImportedThunkBuilder) throws -> String?
29562962
) throws -> (js: [String], dts: [String]) {
@@ -2960,7 +2966,23 @@ extension BridgeJSLink {
29602966
let funcLines = thunkBuilder.renderFunction(
29612967
name: abiName,
29622968
returnExpr: returnExpr,
2963-
returnType: property.type
2969+
returnType: getter.type
2970+
)
2971+
return (funcLines, [])
2972+
}
2973+
2974+
func renderImportedSetter(
2975+
setter: ImportedSetterSkeleton,
2976+
abiName: String,
2977+
emitCall: (ImportedThunkBuilder) throws -> String?
2978+
) throws -> (js: [String], dts: [String]) {
2979+
let thunkBuilder = ImportedThunkBuilder()
2980+
thunkBuilder.liftSelf()
2981+
let returnExpr = try emitCall(thunkBuilder)
2982+
let funcLines = thunkBuilder.renderFunction(
2983+
name: abiName,
2984+
returnExpr: returnExpr,
2985+
returnType: .void
29642986
)
29652987
return (funcLines, [])
29662988
}

Plugins/BridgeJS/Sources/BridgeJSMacros/BridgeJSMacrosPlugin.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ struct BridgeJSMacrosPlugin: CompilerPlugin {
66
var providingMacros: [Macro.Type] = [
77
JSFunctionMacro.self,
88
JSGetterMacro.self,
9+
JSSetterMacro.self,
910
JSClassMacro.self,
1011
]
1112
}

Plugins/BridgeJS/Sources/BridgeJSMacros/JSMacroSupport.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import SwiftSyntaxMacros
44
import SwiftDiagnostics
55

66
enum JSMacroMessage: String, DiagnosticMessage {
7-
case unsupportedDeclaration = "@JSImportFunction can only be applied to functions or initializers."
7+
case unsupportedDeclaration = "@JSFunction can only be applied to functions or initializers."
88
case unsupportedVariable = "@JSGetter can only be applied to single-variable declarations."
9+
case unsupportedSetterDeclaration = "@JSSetter can only be applied to functions."
10+
case invalidSetterName = "@JSSetter function name must start with 'set' followed by a property name (e.g., 'setFoo')."
11+
case setterRequiresParameter = "@JSSetter function must have at least one parameter."
912

1013
var message: String { rawValue }
1114
var diagnosticID: MessageID { MessageID(domain: "JavaScriptKitMacros", id: rawValue) }
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import SwiftSyntax
2+
import SwiftSyntaxBuilder
3+
import SwiftSyntaxMacros
4+
import SwiftDiagnostics
5+
6+
public enum JSSetterMacro {}
7+
8+
extension JSSetterMacro: BodyMacro {
9+
public static func expansion(
10+
of node: AttributeSyntax,
11+
providingBodyFor declaration: some DeclSyntaxProtocol & WithOptionalCodeBlockSyntax,
12+
in context: some MacroExpansionContext
13+
) throws -> [CodeBlockItemSyntax] {
14+
guard let functionDecl = declaration.as(FunctionDeclSyntax.self) else {
15+
context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.unsupportedSetterDeclaration))
16+
return []
17+
}
18+
19+
let functionName = functionDecl.name.text
20+
21+
// Extract property name from setter function name (e.g., "setFoo" -> "foo")
22+
guard functionName.hasPrefix("set"), functionName.count > 3 else {
23+
context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.invalidSetterName))
24+
return []
25+
}
26+
27+
let propertyName = String(functionName.dropFirst(3))
28+
guard !propertyName.isEmpty else {
29+
context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.invalidSetterName))
30+
return []
31+
}
32+
33+
// Convert first character to lowercase (e.g., "Foo" -> "foo")
34+
let baseName = propertyName.prefix(1).lowercased() + propertyName.dropFirst()
35+
36+
let enclosingTypeName = JSMacroHelper.enclosingTypeName(from: context)
37+
let isStatic = JSMacroHelper.isStatic(functionDecl.modifiers)
38+
let isInstanceMember = enclosingTypeName != nil && !isStatic
39+
40+
let glueName = JSMacroHelper.glueName(
41+
baseName: baseName,
42+
enclosingTypeName: enclosingTypeName,
43+
operation: "set"
44+
)
45+
46+
var arguments: [String] = []
47+
if isInstanceMember {
48+
arguments.append("self.jsObject")
49+
}
50+
51+
// Get the parameter name(s) - setters typically have one parameter
52+
let parameters = functionDecl.signature.parameterClause.parameters
53+
guard let firstParam = parameters.first else {
54+
context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.setterRequiresParameter))
55+
return []
56+
}
57+
58+
let paramName = firstParam.secondName ?? firstParam.firstName
59+
arguments.append(paramName.text)
60+
61+
let argsJoined = arguments.joined(separator: ", ")
62+
let call = "\(glueName)(\(argsJoined))"
63+
64+
// Setters should throw JSException, so always use try
65+
return [CodeBlockItemSyntax(stringLiteral: "try \(call)")]
66+
}
67+
}

0 commit comments

Comments
 (0)