Skip to content

Commit f929f40

Browse files
committed
BridgeJS: Runtime tests, string enum fixes and code review feedback
1 parent 6333085 commit f929f40

File tree

11 files changed

+1582
-166
lines changed

11 files changed

+1582
-166
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -865,7 +865,12 @@ public class ExportSwift {
865865
return CodeBlockItemSyntax(item: .init(StmtSyntax("return \(raw: callExpr).jsValue")))
866866
}
867867

868-
let retMutability = returnType == .string ? "var" : "let"
868+
let retMutability: String
869+
if returnType == .string {
870+
retMutability = "var"
871+
} else {
872+
retMutability = "let"
873+
}
869874
if returnType == .void {
870875
return CodeBlockItemSyntax(item: .init(ExpressionStmtSyntax(expression: callExpr)))
871876
} else {
@@ -952,7 +957,8 @@ public class ExportSwift {
952957
if rawType == .string {
953958
append(
954959
"""
955-
return ret.rawValue.withUTF8 { ptr in
960+
var rawValue = ret.rawValue
961+
return rawValue.withUTF8 { ptr in
956962
_swift_js_return_string(ptr.baseAddress, Int32(ptr.count))
957963
}
958964
"""

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ public struct ImportTS {
505505
}
506506
}
507507

508-
extension String {
508+
fileprivate extension String {
509509
func capitalizedFirstLetter() -> String {
510510
guard !isEmpty else { return self }
511511
return prefix(1).uppercased() + dropFirst()

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 89 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ struct BridgeJSLink {
7272
var namespacedEnums: [ExportedEnum] = []
7373
var enumConstantLines: [String] = []
7474
var dtsEnumLines: [String] = []
75+
var topLevelEnumLines: [String] = []
76+
var topLevelDtsEnumLines: [String] = []
7577

7678
if exportedSkeletons.contains(where: { $0.classes.count > 0 }) {
7779
classLines.append(
@@ -102,14 +104,31 @@ struct BridgeJSLink {
102104
if !skeleton.enums.isEmpty {
103105
for enumDefinition in skeleton.enums {
104106
let (jsEnum, dtsEnum) = try renderExportedEnum(enumDefinition)
105-
enumConstantLines.append(contentsOf: jsEnum)
106-
if enumDefinition.enumType != .namespace {
107+
108+
switch enumDefinition.enumType {
109+
case .namespace:
110+
break
111+
case .simple, .rawValue:
112+
// ALL case and raw value enums become top-level exports
113+
var exportedJsEnum = jsEnum
114+
if !exportedJsEnum.isEmpty && exportedJsEnum[0].hasPrefix("const ") {
115+
exportedJsEnum[0] = "export " + exportedJsEnum[0]
116+
}
117+
topLevelEnumLines.append(contentsOf: exportedJsEnum)
118+
topLevelDtsEnumLines.append(contentsOf: dtsEnum)
119+
120+
// Track namespaced enums for globalThis assignments
121+
if enumDefinition.namespace != nil {
122+
namespacedEnums.append(enumDefinition)
123+
}
124+
case .associatedValue:
125+
enumConstantLines.append(contentsOf: jsEnum)
107126
exportsLines.append("\(enumDefinition.name),")
108127
if enumDefinition.namespace != nil {
109128
namespacedEnums.append(enumDefinition)
110129
}
130+
dtsEnumLines.append(contentsOf: dtsEnum)
111131
}
112-
dtsEnumLines.append(contentsOf: dtsEnum)
113132
}
114133
}
115134

@@ -153,10 +172,13 @@ struct BridgeJSLink {
153172

154173
let exportsSection: String
155174
if hasNamespacedItems {
175+
// Only pass associated value enums to renderGlobalNamespace
176+
// (case and raw value enums are handled at top-level)
177+
let namespacedEnumsForExports = namespacedEnums.filter { $0.enumType == .associatedValue }
156178
let namespaceSetupCode = namespaceBuilder.renderGlobalNamespace(
157179
namespacedFunctions: namespacedFunctions,
158180
namespacedClasses: namespacedClasses,
159-
namespacedEnums: namespacedEnums
181+
namespacedEnums: namespacedEnumsForExports
160182
)
161183
.map { $0.indent(count: 12) }.joined(separator: "\n")
162184

@@ -189,14 +211,22 @@ struct BridgeJSLink {
189211
"""
190212
}
191213

214+
let topLevelEnumsSection = topLevelEnumLines.isEmpty ? "" : topLevelEnumLines.joined(separator: "\n") + "\n\n"
215+
216+
let topLevelNamespaceCode = namespaceBuilder.renderTopLevelEnumNamespaceAssignments(
217+
namespacedEnums: namespacedEnums
218+
)
219+
let namespaceAssignmentsSection =
220+
topLevelNamespaceCode.isEmpty ? "" : topLevelNamespaceCode.joined(separator: "\n") + "\n\n"
221+
192222
let outputJs = """
193223
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
194224
// DO NOT EDIT.
195225
//
196226
// To update this file, just rebuild your project or run
197227
// `swift package bridge-js`.
198228
199-
export async function createInstantiator(options, swift) {
229+
\(topLevelEnumsSection)\(namespaceAssignmentsSection)export async function createInstantiator(options, swift) {
200230
let instance;
201231
let memory;
202232
let setException;
@@ -270,14 +300,17 @@ struct BridgeJSLink {
270300
dtsLines.append("export type Imports = {")
271301
dtsLines.append(contentsOf: importObjectBuilders.flatMap { $0.dtsImportLines }.map { $0.indent(count: 4) })
272302
dtsLines.append("}")
303+
let topLevelDtsEnumsSection =
304+
topLevelDtsEnumLines.isEmpty ? "" : topLevelDtsEnumLines.joined(separator: "\n") + "\n"
305+
273306
let outputDts = """
274307
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
275308
// DO NOT EDIT.
276309
//
277310
// To update this file, just rebuild your project or run
278311
// `swift package bridge-js`.
279312
280-
\(dtsLines.joined(separator: "\n"))
313+
\(topLevelDtsEnumsSection)\(dtsLines.joined(separator: "\n"))
281314
export function createInstantiator(options: {
282315
imports: Imports;
283316
}, swift: any): Promise<{
@@ -535,7 +568,7 @@ struct BridgeJSLink {
535568
jsLines.append("const \(enumDefinition.name) = {")
536569
for (index, enumCase) in enumDefinition.cases.enumerated() {
537570
let caseName = enumCase.name.capitalizedFirstLetter
538-
jsLines.append(" \(caseName): \(index),".indent(count: 0))
571+
jsLines.append("\(caseName): \(index),".indent(count: 4))
539572
}
540573
jsLines.append("};")
541574
jsLines.append("")
@@ -546,15 +579,15 @@ struct BridgeJSLink {
546579
dtsLines.append("export enum \(enumDefinition.name) {")
547580
for (index, enumCase) in enumDefinition.cases.enumerated() {
548581
let caseName = enumCase.name.capitalizedFirstLetter
549-
dtsLines.append(" \(caseName) = \(index),")
582+
dtsLines.append("\(caseName) = \(index),".indent(count: 4))
550583
}
551584
dtsLines.append("}")
552585
dtsLines.append("")
553586
case .const:
554587
dtsLines.append("export const \(enumDefinition.name): {")
555588
for (index, enumCase) in enumDefinition.cases.enumerated() {
556589
let caseName = enumCase.name.capitalizedFirstLetter
557-
dtsLines.append(" readonly \(caseName): \(index);")
590+
dtsLines.append("readonly \(caseName): \(index);".indent(count: 4))
558591
}
559592
dtsLines.append("};")
560593
dtsLines.append(
@@ -608,7 +641,7 @@ struct BridgeJSLink {
608641
case "Float", "Double": formattedValue = rawValue
609642
default: formattedValue = rawValue
610643
}
611-
dtsLines.append(" \(caseName) = \(formattedValue),")
644+
dtsLines.append("\(caseName) = \(formattedValue),".indent(count: 4))
612645
}
613646
dtsLines.append("}")
614647
dtsLines.append("")
@@ -630,7 +663,7 @@ struct BridgeJSLink {
630663
formattedValue = rawValue
631664
}
632665

633-
dtsLines.append(" readonly \(caseName): \(formattedValue);")
666+
dtsLines.append("readonly \(caseName): \(formattedValue);".indent(count: 4))
634667
}
635668
dtsLines.append("};")
636669
dtsLines.append(
@@ -780,7 +813,7 @@ struct BridgeJSLink {
780813

781814
uniqueNamespaces.sorted().forEach { namespace in
782815
lines.append("if (typeof globalThis.\(namespace) === 'undefined') {")
783-
lines.append(" globalThis.\(namespace) = {};")
816+
lines.append("globalThis.\(namespace) = {};".indent(count: 4))
784817
lines.append("}")
785818
}
786819

@@ -790,8 +823,11 @@ struct BridgeJSLink {
790823
}
791824

792825
namespacedEnums.forEach { enumDefinition in
793-
let namespacePath: String = enumDefinition.namespace?.joined(separator: ".") ?? ""
794-
lines.append("globalThis.\(namespacePath).\(enumDefinition.name) = exports.\(enumDefinition.name);")
826+
// Only handle associated value enums here (case and raw value enums are handled at top-level)
827+
if enumDefinition.enumType == .associatedValue {
828+
let namespacePath: String = enumDefinition.namespace?.joined(separator: ".") ?? ""
829+
lines.append("globalThis.\(namespacePath).\(enumDefinition.name) = exports.\(enumDefinition.name);")
830+
}
795831
}
796832

797833
namespacedFunctions.forEach { function in
@@ -1015,7 +1051,7 @@ struct BridgeJSLink {
10151051

10161052
uniqueNamespaces.sorted().forEach { namespace in
10171053
lines.append("if (typeof globalThis.\(namespace) === 'undefined') {")
1018-
lines.append(" globalThis.\(namespace) = {};")
1054+
lines.append("globalThis.\(namespace) = {};".indent(count: 4))
10191055
lines.append("}")
10201056
}
10211057

@@ -1037,6 +1073,44 @@ struct BridgeJSLink {
10371073
return lines
10381074
}
10391075

1076+
func renderTopLevelEnumNamespaceAssignments(namespacedEnums: [ExportedEnum]) -> [String] {
1077+
let topLevelNamespacedEnums = namespacedEnums.filter { $0.enumType == .simple || $0.enumType == .rawValue }
1078+
1079+
guard !topLevelNamespacedEnums.isEmpty else { return [] }
1080+
1081+
var lines: [String] = []
1082+
var uniqueNamespaces: [String] = []
1083+
var seen = Set<String>()
1084+
1085+
for enumDef in topLevelNamespacedEnums {
1086+
guard let namespacePath = enumDef.namespace else { continue }
1087+
namespacePath.enumerated().forEach { (index, _) in
1088+
let path = namespacePath[0...index].joined(separator: ".")
1089+
if !seen.contains(path) {
1090+
seen.insert(path)
1091+
uniqueNamespaces.append(path)
1092+
}
1093+
}
1094+
}
1095+
1096+
for namespace in uniqueNamespaces {
1097+
lines.append("if (typeof globalThis.\(namespace) === 'undefined') {")
1098+
lines.append("globalThis.\(namespace) = {};".indent(count: 4))
1099+
lines.append("}")
1100+
}
1101+
1102+
if !lines.isEmpty {
1103+
lines.append("")
1104+
}
1105+
1106+
for enumDef in topLevelNamespacedEnums {
1107+
let namespacePath = enumDef.namespace?.joined(separator: ".") ?? ""
1108+
lines.append("globalThis.\(namespacePath).\(enumDef.name) = \(enumDef.name);")
1109+
}
1110+
1111+
return lines
1112+
}
1113+
10401114
private struct NamespaceContent {
10411115
var functions: [ExportedFunction] = []
10421116
var classes: [ExportedClass] = []

Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumNamespace.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
// Invalid to declare @JS(namespace) here as it would be conflicting with nesting
2222
@JS class HTTPServer {
2323
@JS init() {}
24-
@JS func call(_ method: Method)
24+
@JS func call(_ method: Method) {}
2525
}
2626
}
2727
}
@@ -49,7 +49,7 @@ enum Internal {
4949
}
5050
@JS class TestServer {
5151
@JS init() {}
52-
@JS func call(_ method: SupportedMethod)
52+
@JS func call(_ method: SupportedMethod) {}
5353
}
5454
}
5555

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,27 @@
44
// To update this file, just rebuild your project or run
55
// `swift package bridge-js`.
66

7+
export const Direction = {
8+
North: 0,
9+
South: 1,
10+
East: 2,
11+
West: 3,
12+
};
13+
14+
export const Status = {
15+
Loading: 0,
16+
Success: 1,
17+
Error: 2,
18+
};
19+
20+
export const TSDirection = {
21+
North: 0,
22+
South: 1,
23+
East: 2,
24+
West: 3,
25+
};
26+
27+
728
export async function createInstantiator(options, swift) {
829
let instance;
930
let memory;
@@ -63,30 +84,7 @@ export async function createInstantiator(options, swift) {
6384
createExports: (instance) => {
6485
const js = swift.memory.heap;
6586

66-
const Direction = {
67-
North: 0,
68-
South: 1,
69-
East: 2,
70-
West: 3,
71-
};
72-
73-
const Status = {
74-
Loading: 0,
75-
Success: 1,
76-
Error: 2,
77-
};
78-
79-
const TSDirection = {
80-
North: 0,
81-
South: 1,
82-
East: 2,
83-
West: 3,
84-
};
85-
8687
return {
87-
Direction,
88-
Status,
89-
TSDirection,
9088
setDirection: function bjs_setDirection(direction) {
9189
instance.exports.bjs_setDirection(direction | 0);
9290
},

0 commit comments

Comments
 (0)