diff --git a/Sources/CodableToTypeScript/TypeConverter/EnumConverter.swift b/Sources/CodableToTypeScript/TypeConverter/EnumConverter.swift index 84aa683..ff61ca0 100644 --- a/Sources/CodableToTypeScript/TypeConverter/EnumConverter.swift +++ b/Sources/CodableToTypeScript/TypeConverter/EnumConverter.swift @@ -2,14 +2,30 @@ import SwiftTypeReader import TypeScriptAST public struct EnumConverter: TypeConverter { - public init(generator: CodeGenerator, `enum`: EnumType) { + public enum EmptyEnumStrategy { + case never + case void + + func toKind() -> Kind { + switch self { + case .never: return .never + case .void: return .void + } + } + } + + public init( + generator: CodeGenerator, + `enum`: EnumType, + emptyEnumStrategy: EmptyEnumStrategy = .never + ) { self.generator = generator self.`enum` = `enum` let decl = `enum`.decl if decl.caseElements.isEmpty { - self.kind = .never + self.kind = emptyEnumStrategy.toKind() return } @@ -37,6 +53,7 @@ public struct EnumConverter: TypeConverter { enum Kind { case never + case void case string case int case normal @@ -61,6 +78,17 @@ public struct EnumConverter: TypeConverter { genericParams: genericParams, type: TSIdentType.never ) + case .void: + var type: any TSType = TSIdentType.void + if target == .entity { + type = try attachTag(to: type) + } + return TSTypeDecl( + modifiers: [.export], + name: try name(for: target), + genericParams: genericParams, + type: type + ) case .string: let items: [any TSType] = decl.caseElements.map { (ce) in TSStringLiteralType(ce.name) @@ -117,13 +145,7 @@ public struct EnumConverter: TypeConverter { switch target { case .entity: - let tag = try generator.tagRecord( - name: name, - genericArgs: try self.genericParams().map { - try TSIdentType($0.name(for: .entity)) - } - ) - type = TSIntersectionType(type, tag) + type = try attachTag(to: type) case .json: break } @@ -135,6 +157,18 @@ public struct EnumConverter: TypeConverter { ) } + private func attachTag(to type: any TSType) throws -> any TSType { + let target = GenerationTarget.entity + let name = try self.name(for: target) + let tag = try generator.tagRecord( + name: name, + genericArgs: try self.genericParams().map { + try TSIdentType($0.name(for: target)) + } + ) + return TSIntersectionType(type, tag) + } + private func transpile( caseElement: EnumCaseElementDecl, target: GenerationTarget @@ -184,6 +218,7 @@ public struct EnumConverter: TypeConverter { public func hasDecode() throws -> Bool { switch kind { case .never: return false + case .void: return false case .string: return false case .int: return true case .normal: return true @@ -192,7 +227,7 @@ public struct EnumConverter: TypeConverter { public func decodeDecl() throws -> TSFunctionDecl? { switch kind { - case .never, .string: + case .never, .void, .string: return nil case .int: return try DecodeIntFuncGen( @@ -211,6 +246,7 @@ public struct EnumConverter: TypeConverter { public func hasEncode() throws -> Bool { switch kind { case .never: return false + case .void: return false case .string: return false case .int: return true case .normal: break @@ -238,7 +274,7 @@ public struct EnumConverter: TypeConverter { public func encodeDecl() throws -> TSFunctionDecl? { switch kind { - case .never, .string: + case .never, .void, .string: return nil case .int: return try EncodeIntFuncGen( diff --git a/Sources/CodableToTypeScript/TypeConverter/TypeConverterProvider.swift b/Sources/CodableToTypeScript/TypeConverter/TypeConverterProvider.swift index 8200bc8..11a5330 100644 --- a/Sources/CodableToTypeScript/TypeConverter/TypeConverterProvider.swift +++ b/Sources/CodableToTypeScript/TypeConverter/TypeConverterProvider.swift @@ -1,7 +1,7 @@ import SwiftTypeReader public struct TypeConverterProvider { - public typealias CustomProvider = (CodeGenerator, any SType) -> (any TypeConverter)? + public typealias CustomProvider = (CodeGenerator, any SType) throws -> (any TypeConverter)? public init( typeMap: TypeMap = .default, @@ -19,12 +19,26 @@ public struct TypeConverterProvider { type: any SType ) throws -> any TypeConverter { if let customProvider, - let converter = customProvider(generator, type) + let converter = try customProvider(generator, type) { return converter } else if let entry = typeMap.map(type: type) { return TypeMapConverter(generator: generator, type: type, entry: entry) - } else if type.isStandardLibraryType("Optional") { + } else if let converter = try Self.defaultConverter( + generator: generator, + type: type + ) { + return converter + } else { + throw MessageError("Unsupported type: \(type)") + } + } + + public static func defaultConverter( + generator: CodeGenerator, + type: any SType + ) throws -> (any TypeConverter)? { + if type.isStandardLibraryType("Optional") { return OptionalConverter(generator: generator, swiftType: type) } else if type.isStandardLibraryType("Array") { return ArrayConverter(generator: generator, swiftType: type) @@ -42,7 +56,6 @@ public struct TypeConverterProvider { rawValueType: raw ) } - return StructConverter(generator: generator, struct: type) } else if let type = type.asGenericParam { return GenericParamConverter(generator: generator, param: type) @@ -51,7 +64,7 @@ public struct TypeConverterProvider { } else if let type = type.asError { return ErrorTypeConverter(generator: generator, swiftType: type) } else { - throw MessageError("Unsupported type: \(type)") + return nil } } } diff --git a/Tests/CodableToTypeScriptTests/Generate/GenerateEnumTests.swift b/Tests/CodableToTypeScriptTests/Generate/GenerateEnumTests.swift index 6b71ddc..4b1d6e3 100644 --- a/Tests/CodableToTypeScriptTests/Generate/GenerateEnumTests.swift +++ b/Tests/CodableToTypeScriptTests/Generate/GenerateEnumTests.swift @@ -375,4 +375,43 @@ e: e2 unexpecteds: [] ) } + + func testEmptyEnumNever() throws { + try assertGenerate( + source: """ +enum E {} +""", + expecteds: [ +""" +export type E = never; +""" + ] + ) + } + + func testEmptyEnumVoid() throws { + let typeConverterProvider = TypeConverterProvider { (gen, ty) in + guard let cnv = try TypeConverterProvider.defaultConverter(generator: gen, type: ty) else { + return nil + } + + if let cnv = cnv as? EnumConverter { + return EnumConverter(generator: gen, enum: cnv.enum, emptyEnumStrategy: .void) + } + + return nil + } + + try assertGenerate( + source: """ +enum E {} +""", + typeConverterProvider: typeConverterProvider, + expecteds: [ +""" +export type E = void & TagRecord<"E">; +""" + ] + ) + } }