Skip to content

Commit ee6b207

Browse files
committed
BridgeJS: Move JS code generation to helpers
1 parent 95f6869 commit ee6b207

File tree

3 files changed

+459
-387
lines changed

3 files changed

+459
-387
lines changed

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 105 additions & 232 deletions
Original file line numberDiff line numberDiff line change
@@ -554,232 +554,118 @@ struct BridgeJSLink {
554554
func renderExportedEnum(_ enumDefinition: ExportedEnum) throws -> (js: [String], dts: [String]) {
555555
var jsLines: [String] = []
556556
var dtsLines: [String] = []
557-
let style: EnumEmitStyle = enumDefinition.emitStyle
557+
let scope = JSGlueVariableScope()
558+
let cleanup = CodeFragmentPrinter()
559+
let printer = CodeFragmentPrinter()
558560

559561
switch enumDefinition.enumType {
560562
case .simple:
561-
jsLines.append("const \(enumDefinition.name) = {")
562-
for (index, enumCase) in enumDefinition.cases.enumerated() {
563-
let caseName = enumCase.name.capitalizedFirstLetter
564-
jsLines.append("\(caseName): \(index),".indent(count: 4))
565-
}
566-
jsLines.append("};")
567-
jsLines.append("")
568-
569-
if enumDefinition.namespace == nil {
570-
switch style {
571-
case .tsEnum:
572-
dtsLines.append("export enum \(enumDefinition.name) {")
573-
for (index, enumCase) in enumDefinition.cases.enumerated() {
574-
let caseName = enumCase.name.capitalizedFirstLetter
575-
dtsLines.append("\(caseName) = \(index),".indent(count: 4))
576-
}
577-
dtsLines.append("}")
578-
dtsLines.append("")
579-
case .const:
580-
dtsLines.append("export const \(enumDefinition.name): {")
581-
for (index, enumCase) in enumDefinition.cases.enumerated() {
582-
let caseName = enumCase.name.capitalizedFirstLetter
583-
dtsLines.append("readonly \(caseName): \(index);".indent(count: 4))
584-
}
585-
dtsLines.append("};")
586-
dtsLines.append(
587-
"export type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];"
588-
)
589-
dtsLines.append("")
590-
}
591-
}
563+
let fragment = IntrinsicJSFragment.simpleEnumHelper(enumDefinition: enumDefinition)
564+
_ = fragment.printCode([enumDefinition.name], scope, printer, cleanup)
565+
566+
jsLines.append(contentsOf: printer.lines)
592567
case .rawValue:
593-
guard let rawType = enumDefinition.rawType else {
568+
guard enumDefinition.rawType != nil else {
594569
throw BridgeJSLinkError(message: "Raw value enum \(enumDefinition.name) is missing rawType")
595570
}
596571

597-
jsLines.append("const \(enumDefinition.name) = {")
598-
for enumCase in enumDefinition.cases {
599-
let caseName = enumCase.name.capitalizedFirstLetter
600-
let rawValue = enumCase.rawValue ?? enumCase.name
601-
let formattedValue: String
602-
603-
if let rawTypeEnum = SwiftEnumRawType.from(rawType) {
604-
switch rawTypeEnum {
605-
case .string:
606-
formattedValue = "\"\(rawValue)\""
607-
case .bool:
608-
formattedValue = rawValue.lowercased() == "true" ? "true" : "false"
609-
case .float, .double:
610-
formattedValue = rawValue
611-
default:
612-
formattedValue = rawValue
613-
}
614-
} else {
615-
formattedValue = rawValue
616-
}
572+
let fragment = IntrinsicJSFragment.rawValueEnumHelper(enumDefinition: enumDefinition)
573+
_ = fragment.printCode([enumDefinition.name], scope, printer, cleanup)
617574

618-
jsLines.append("\(caseName): \(formattedValue),".indent(count: 4))
619-
}
620-
jsLines.append("};")
621-
jsLines.append("")
622-
623-
if enumDefinition.namespace == nil {
624-
switch style {
625-
case .tsEnum:
626-
dtsLines.append("export enum \(enumDefinition.name) {")
627-
for enumCase in enumDefinition.cases {
628-
let caseName = enumCase.name.capitalizedFirstLetter
575+
jsLines.append(contentsOf: printer.lines)
576+
case .associatedValue:
577+
let fragment = IntrinsicJSFragment.associatedValueEnumHelper(enumDefinition: enumDefinition)
578+
_ = fragment.printCode([enumDefinition.name], scope, printer, cleanup)
579+
580+
jsLines.append(contentsOf: printer.lines)
581+
case .namespace:
582+
break
583+
}
584+
585+
if enumDefinition.namespace == nil {
586+
dtsLines.append(contentsOf: generateDeclarations(enumDefinition: enumDefinition))
587+
}
588+
589+
return (jsLines, dtsLines)
590+
}
591+
592+
private func generateDeclarations(enumDefinition: ExportedEnum) -> [String] {
593+
let printer = CodeFragmentPrinter()
594+
595+
switch enumDefinition.emitStyle {
596+
case .tsEnum:
597+
switch enumDefinition.enumType {
598+
case .simple, .rawValue:
599+
printer.write("export enum \(enumDefinition.name) {")
600+
printer.indent()
601+
for (index, enumCase) in enumDefinition.cases.enumerated() {
602+
let caseName = enumCase.name.capitalizedFirstLetter
603+
let value: String
604+
605+
switch enumDefinition.enumType {
606+
case .simple:
607+
value = "\(index)"
608+
case .rawValue:
629609
let rawValue = enumCase.rawValue ?? enumCase.name
630-
let formattedValue: String
631-
switch rawType {
632-
case "String": formattedValue = "\"\(rawValue)\""
633-
case "Bool": formattedValue = rawValue.lowercased() == "true" ? "true" : "false"
634-
case "Float", "Double": formattedValue = rawValue
635-
default: formattedValue = rawValue
636-
}
637-
dtsLines.append("\(caseName) = \(formattedValue),".indent(count: 4))
610+
value = SwiftEnumRawType.formatValue(rawValue, rawType: enumDefinition.rawType ?? "")
611+
case .associatedValue, .namespace:
612+
continue
638613
}
639-
dtsLines.append("}")
640-
dtsLines.append("")
641-
case .const:
642-
dtsLines.append("export const \(enumDefinition.name): {")
643-
for enumCase in enumDefinition.cases {
644-
let caseName = enumCase.name.capitalizedFirstLetter
645-
let rawValue = enumCase.rawValue ?? enumCase.name
646-
let formattedValue: String
647-
648-
switch rawType {
649-
case "String":
650-
formattedValue = "\"\(rawValue)\""
651-
case "Bool":
652-
formattedValue = rawValue.lowercased() == "true" ? "true" : "false"
653-
case "Float", "Double":
654-
formattedValue = rawValue
655-
default:
656-
formattedValue = rawValue
657-
}
658614

659-
dtsLines.append("readonly \(caseName): \(formattedValue);".indent(count: 4))
660-
}
661-
dtsLines.append("};")
662-
dtsLines.append(
663-
"export type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];"
664-
)
665-
dtsLines.append("")
615+
printer.write("\(caseName) = \(value),")
666616
}
617+
printer.unindent()
618+
printer.write("}")
619+
printer.write("")
620+
case .associatedValue, .namespace:
621+
break
667622
}
668-
case .associatedValue:
669-
// Use the new IntrinsicJSFragment for associated value payload handling
670-
jsLines.append("const \(enumDefinition.name) = {")
671-
jsLines.append("Tag: {".indent(count: 4))
672-
for (index, enumCase) in enumDefinition.cases.enumerated() {
673-
let caseName = enumCase.name.capitalizedFirstLetter
674-
jsLines.append("\(caseName): \(index),".indent(count: 8))
675-
}
676-
jsLines.append("}".indent(count: 4))
677-
jsLines.append("};")
678-
jsLines.append("")
679-
jsLines.append("const __bjs_create\(enumDefinition.name)Helpers = () => {")
680-
jsLines.append(
681-
"return (\(JSGlueVariableScope.reservedTmpParamInts), \(JSGlueVariableScope.reservedTmpParamF32s), \(JSGlueVariableScope.reservedTmpParamF64s), textEncoder, \(JSGlueVariableScope.reservedSwift)) => ({"
682-
.indent(
683-
count: 4
684-
)
685-
)
686623

687-
jsLines.append("lower: (value) => {".indent(count: 8))
688-
jsLines.append("const enumTag = value.tag;".indent(count: 12))
689-
jsLines.append("switch (enumTag) {".indent(count: 12))
690-
enumDefinition.cases.forEach { enumCase in
691-
let caseName = enumCase.name.capitalizedFirstLetter
692-
if enumCase.associatedValues.isEmpty {
693-
jsLines.append("case \(enumDefinition.name).Tag.\(caseName): {".indent(count: 16))
694-
jsLines.append("const cleanup = undefined;".indent(count: 20))
695-
jsLines.append(
696-
"return { caseId: \(enumDefinition.name).Tag.\(caseName), cleanup };"
697-
.indent(count: 20)
698-
)
699-
jsLines.append("}".indent(count: 16))
700-
} else {
701-
let scope = JSGlueVariableScope()
702-
let cleanup = CodeFragmentPrinter()
703-
let printer = CodeFragmentPrinter()
704-
cleanup.indent()
705-
706-
let fragment = IntrinsicJSFragment.associatedValuePushPayload(enumCase: enumCase)
707-
_ = fragment.printCode(["value", enumDefinition.name, caseName], scope, printer, cleanup)
708-
709-
jsLines.append("case \(enumDefinition.name).Tag.\(caseName): {".indent(count: 16))
710-
jsLines.append(contentsOf: printer.lines.map { $0.indent(count: 20) })
711-
jsLines.append("}".indent(count: 16))
624+
case .const:
625+
switch enumDefinition.enumType {
626+
case .simple:
627+
printer.write("export const \(enumDefinition.name): {")
628+
printer.indent()
629+
for (index, enumCase) in enumDefinition.cases.enumerated() {
630+
let caseName = enumCase.name.capitalizedFirstLetter
631+
printer.write("readonly \(caseName): \(index);")
712632
}
713-
}
714-
jsLines.append(
715-
"default: throw new Error(\"Unknown \(enumDefinition.name) tag: \" + String(enumTag));".indent(
716-
count: 16
633+
printer.unindent()
634+
printer.write("};")
635+
printer.write(
636+
"export type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];"
717637
)
718-
)
719-
jsLines.append("}".indent(count: 12))
720-
jsLines.append("},".indent(count: 8))
721-
722-
jsLines.append(
723-
"raise: (\(JSGlueVariableScope.reservedTmpRetTag), \(JSGlueVariableScope.reservedTmpRetStrings), \(JSGlueVariableScope.reservedTmpRetInts), \(JSGlueVariableScope.reservedTmpRetF32s), \(JSGlueVariableScope.reservedTmpRetF64s)) => {"
724-
.indent(
725-
count: 8
726-
)
727-
)
728-
jsLines.append("const tag = tmpRetTag | 0;".indent(count: 12))
729-
jsLines.append("switch (tag) {".indent(count: 12))
730-
enumDefinition.cases.forEach { enumCase in
731-
let caseName = enumCase.name.capitalizedFirstLetter
732-
if enumCase.associatedValues.isEmpty {
733-
jsLines.append(
734-
"case \(enumDefinition.name).Tag.\(caseName): return { tag: \(enumDefinition.name).Tag.\(caseName) };"
735-
.indent(count: 16)
736-
)
737-
} else {
738-
var fieldPairs: [String] = []
739-
let scope = JSGlueVariableScope()
740-
let printer = CodeFragmentPrinter()
741-
let cleanup = CodeFragmentPrinter()
742-
// Use the new IntrinsicJSFragment for associated value payload handling
743-
for (associatedValueIndex, associatedValue) in enumCase.associatedValues.enumerated().reversed() {
744-
let prop = associatedValue.label ?? "param\(associatedValueIndex)"
745-
let fragment = IntrinsicJSFragment.associatedValuePopPayload(type: associatedValue.type)
746-
747-
let result = fragment.printCode([], scope, printer, cleanup)
748-
let varName = result.first ?? "value_\(associatedValueIndex)"
749-
750-
fieldPairs.append("\(prop): \(varName)")
751-
}
752-
753-
jsLines.append("case \(enumDefinition.name).Tag.\(caseName): {".indent(count: 16))
754-
jsLines.append(contentsOf: printer.lines.map { $0.indent(count: 20) })
755-
jsLines.append(
756-
"return { tag: \(enumDefinition.name).Tag.\(caseName), \(fieldPairs.reversed().joined(separator: ", ")) };"
757-
.indent(count: 20)
758-
)
759-
jsLines.append("}".indent(count: 16))
638+
printer.write("")
639+
case .rawValue:
640+
printer.write("export const \(enumDefinition.name): {")
641+
printer.indent()
642+
for enumCase in enumDefinition.cases {
643+
let caseName = enumCase.name.capitalizedFirstLetter
644+
let rawValue = enumCase.rawValue ?? enumCase.name
645+
let formattedValue = SwiftEnumRawType.formatValue(rawValue, rawType: enumDefinition.rawType ?? "")
646+
printer.write("readonly \(caseName): \(formattedValue);")
760647
}
761-
}
762-
jsLines.append(
763-
"default: throw new Error(\"Unknown \(enumDefinition.name) tag returned from Swift: \" + String(tag));"
764-
.indent(
765-
count: 16
766-
)
767-
)
768-
jsLines.append("}".indent(count: 12))
769-
jsLines.append("}".indent(count: 8))
770-
jsLines.append("});".indent(count: 4))
771-
jsLines.append("};")
772-
773-
if enumDefinition.namespace == nil {
774-
dtsLines.append("export const \(enumDefinition.name): {")
775-
dtsLines.append("readonly Tag: {".indent(count: 4))
648+
printer.unindent()
649+
printer.write("};")
650+
printer.write(
651+
"export type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];"
652+
)
653+
printer.write("")
654+
case .associatedValue:
655+
printer.write("export const \(enumDefinition.name): {")
656+
printer.indent()
657+
printer.write("readonly Tag: {")
658+
printer.indent()
776659
for (index, enumCase) in enumDefinition.cases.enumerated() {
777660
let caseName = enumCase.name.capitalizedFirstLetter
778-
dtsLines.append("readonly \(caseName): \(index);".indent(count: 8))
661+
printer.write("readonly \(caseName): \(index);")
779662
}
780-
dtsLines.append("};".indent(count: 4))
781-
dtsLines.append("};")
782-
dtsLines.append("")
663+
printer.unindent()
664+
printer.write("};")
665+
printer.unindent()
666+
printer.write("};")
667+
printer.write("")
668+
783669
var unionParts: [String] = []
784670
for enumCase in enumDefinition.cases {
785671
if enumCase.associatedValues.isEmpty {
@@ -790,32 +676,26 @@ struct BridgeJSLink {
790676
var fields: [String] = [
791677
"tag: typeof \(enumDefinition.name).Tag.\(enumCase.name.capitalizedFirstLetter)"
792678
]
793-
for (associatedValueIndex, associatedValue) in enumCase.associatedValues
794-
.enumerated()
795-
{
679+
for (associatedValueIndex, associatedValue) in enumCase.associatedValues.enumerated() {
796680
let prop = associatedValue.label ?? "param\(associatedValueIndex)"
797-
let ts: String
798-
switch associatedValue.type {
799-
case .string: ts = "string"
800-
case .bool: ts = "boolean"
801-
case .int, .float, .double: ts = "number"
802-
default: ts = "never"
803-
}
804-
fields.append("\(prop): \(ts)")
681+
fields.append("\(prop): \(associatedValue.type.tsType)")
805682
}
806683
unionParts.append("{ \(fields.joined(separator: "; ")) }")
807684
}
808685
}
809-
dtsLines.append("export type \(enumDefinition.name) =")
810-
dtsLines.append(" " + unionParts.joined(separator: " | "))
811-
dtsLines.append("")
686+
687+
printer.write("export type \(enumDefinition.name) =")
688+
printer.write(" " + unionParts.joined(separator: " | "))
689+
printer.write("")
690+
case .namespace:
691+
break
812692
}
813-
case .namespace:
814-
break
815693
}
816-
817-
return (jsLines, dtsLines)
694+
return printer.lines
818695
}
696+
}
697+
698+
extension BridgeJSLink {
819699

820700
func renderExportedFunction(function: ExportedFunction) throws -> (js: [String], dts: [String]) {
821701
let thunkBuilder = ExportedThunkBuilder(effects: function.effects)
@@ -1502,14 +1382,7 @@ struct BridgeJSLink {
15021382
.enumerated()
15031383
{
15041384
let prop = associatedValue.label ?? "param\(associatedValueIndex)"
1505-
let ts: String
1506-
switch associatedValue.type {
1507-
case .string: ts = "string"
1508-
case .bool: ts = "boolean"
1509-
case .int, .float, .double: ts = "number"
1510-
default: ts = "never"
1511-
}
1512-
fields.append("\(prop): \(ts)")
1385+
fields.append("\(prop): \(associatedValue.type.tsType)")
15131386
}
15141387
unionParts.append("{ \(fields.joined(separator: "; ")) }")
15151388
}

0 commit comments

Comments
 (0)