Skip to content

Commit 9837d89

Browse files
committed
BridgeJS: Support TS enum style syntax for raw type string and numeric type + docs
1 parent 46c64db commit 9837d89

File tree

20 files changed

+1195
-88
lines changed

20 files changed

+1195
-88
lines changed

Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
{
22
"classes" : [
33

4+
],
5+
"enums" : [
6+
47
],
58
"functions" : [
69
{

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
}
4747
}
4848
],
49-
"name" : "PlayBridgeJS"
49+
"name" : "PlayBridgeJS",
50+
"swiftCallName" : "PlayBridgeJS"
5051
},
5152
{
5253
"methods" : [
@@ -115,8 +116,12 @@
115116
}
116117
}
117118
],
118-
"name" : "PlayBridgeJSOutput"
119+
"name" : "PlayBridgeJSOutput",
120+
"swiftCallName" : "PlayBridgeJSOutput"
119121
}
122+
],
123+
"enums" : [
124+
120125
],
121126
"functions" : [
122127

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,24 @@ public class ExportSwift {
267267
return namespaceString.split(separator: ".").map(String.init)
268268
}
269269

270+
private func extractEnumStyle(
271+
from jsAttribute: AttributeSyntax
272+
) -> EnumEmitStyle? {
273+
guard let arguments = jsAttribute.arguments?.as(LabeledExprListSyntax.self),
274+
let styleArg = arguments.first(where: { $0.label?.text == "enumStyle" })
275+
else {
276+
return nil
277+
}
278+
let text = styleArg.expression.trimmedDescription
279+
if text.contains("tsEnum") {
280+
return .tsEnum
281+
}
282+
if text.contains("const") {
283+
return .const
284+
}
285+
return nil
286+
}
287+
270288
override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind {
271289
guard node.attributes.hasJSAttribute() else { return .skipChildren }
272290
guard case .classBody(let className, _) = state else {
@@ -318,9 +336,6 @@ public class ExportSwift {
318336
let name = node.name.text
319337

320338
guard let jsAttribute = node.attributes.firstJSAttribute else {
321-
if case .enumBody(_) = state {
322-
return .skipChildren
323-
}
324339
return .skipChildren
325340
}
326341

@@ -355,14 +370,14 @@ public class ExportSwift {
355370
}
356371

357372
override func visitPost(_ node: ClassDeclSyntax) {
358-
stateStack.pop()
373+
// Make sure we pop the state stack only if we're in a class body state (meaning we successfully pushed)
374+
if case .classBody(_, _) = stateStack.current {
375+
stateStack.pop()
376+
}
359377
}
360378

361379
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
362380
guard node.attributes.hasJSAttribute() else {
363-
if case .enumBody(_) = state {
364-
return .skipChildren
365-
}
366381
return .skipChildren
367382
}
368383

@@ -402,6 +417,10 @@ public class ExportSwift {
402417
guard let jsAttribute = node.attributes.firstJSAttribute,
403418
let enumName = currentEnum.name
404419
else {
420+
// Only pop if we have a valid enum that was processed
421+
if case .enumBody(_) = stateStack.current {
422+
stateStack.pop()
423+
}
405424
return
406425
}
407426

@@ -415,13 +434,26 @@ public class ExportSwift {
415434
effectiveNamespace = computedNamespace
416435
}
417436

437+
let emitStyle = extractEnumStyle(from: jsAttribute) ?? .const
438+
if case .tsEnum = emitStyle,
439+
let raw = currentEnum.rawType,
440+
let rawEnum = SwiftEnumRawType.from(raw), rawEnum == .bool
441+
{
442+
diagnose(
443+
node: jsAttribute,
444+
message: "TypeScript enum style is not supported for Bool raw-value enums",
445+
hint: "Use enumStyle: .const or change the raw type to String or a numeric type"
446+
)
447+
}
448+
418449
let swiftCallName = ExportSwift.computeSwiftCallName(for: node, itemName: enumName)
419450
let exportedEnum = ExportedEnum(
420451
name: enumName,
421452
swiftCallName: swiftCallName,
422453
cases: currentEnum.cases,
423454
rawType: currentEnum.rawType,
424-
namespace: effectiveNamespace
455+
namespace: effectiveNamespace,
456+
emitStyle: emitStyle
425457
)
426458
exportedEnumByName[enumName] = exportedEnum
427459
exportedEnumNames.append(enumName)
@@ -614,6 +646,11 @@ public class ExportSwift {
614646
return nil
615647
}
616648
decls.append(Self.prelude)
649+
650+
for enumDef in exportedEnums where enumDef.enumType == .simple {
651+
decls.append(renderCaseEnumHelpers(enumDef))
652+
}
653+
617654
for function in exportedFunctions {
618655
decls.append(renderSingleExportedFunction(function: function))
619656
}
@@ -624,6 +661,32 @@ public class ExportSwift {
624661
return decls.map { $0.formatted(using: format).description }.joined(separator: "\n\n")
625662
}
626663

664+
func renderCaseEnumHelpers(_ enumDef: ExportedEnum) -> DeclSyntax {
665+
let typeName = enumDef.swiftCallName
666+
var initCases: [String] = []
667+
var valueCases: [String] = []
668+
for (index, c) in enumDef.cases.enumerated() {
669+
initCases.append("case \(index): self = .\(c.name)")
670+
valueCases.append("case .\(c.name): return \(index)")
671+
}
672+
let initSwitch = (["switch bridgeJSRawValue {"] + initCases + ["default: return nil", "}"]).joined(
673+
separator: "\n"
674+
)
675+
let valueSwitch = (["switch self {"] + valueCases + ["}"]).joined(separator: "\n")
676+
677+
return """
678+
extension \(raw: typeName) {
679+
init?(bridgeJSRawValue: Int32) {
680+
\(raw: initSwitch)
681+
}
682+
683+
var bridgeJSRawValue: Int32 {
684+
\(raw: valueSwitch)
685+
}
686+
}
687+
"""
688+
}
689+
627690
class ExportedThunkBuilder {
628691
var body: [CodeBlockItemSyntax] = []
629692
var abiParameterForwardings: [LabeledExprSyntax] = []
@@ -700,7 +763,7 @@ public class ExportSwift {
700763
abiParameterForwardings.append(
701764
LabeledExprSyntax(
702765
label: param.label,
703-
expression: ExprSyntax("\(raw: enumName)(rawValue: Int(\(raw: param.name)))!")
766+
expression: ExprSyntax("\(raw: enumName)(bridgeJSRawValue: \(raw: param.name))!")
704767
)
705768
)
706769
abiParameterSignatures.append((param.name, .i32))
@@ -770,7 +833,6 @@ public class ExportSwift {
770833
)
771834
abiParameterSignatures.append((param.name, .i32))
772835
case .swiftHeapObject:
773-
// UnsafeMutableRawPointer is passed as an i32 pointer
774836
let objectExpr: ExprSyntax =
775837
"Unmanaged<\(raw: param.type.swiftType)>.fromOpaque(\(raw: param.name)).takeUnretainedValue()"
776838
abiParameterForwardings.append(
@@ -885,7 +947,7 @@ public class ExportSwift {
885947
)
886948
case .caseEnum:
887949
abiReturnType = .i32
888-
append("return Int32(ret.rawValue)")
950+
append("return ret.bridgeJSRawValue")
889951
case .rawValueEnum(_, let rawType):
890952
if rawType == .string {
891953
append(

0 commit comments

Comments
 (0)