Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 47 additions & 11 deletions Sources/CodableToTypeScript/TypeConverter/EnumConverter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -37,6 +53,7 @@ public struct EnumConverter: TypeConverter {

enum Kind {
case never
case void
case string
case int
case normal
Expand All @@ -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)
Expand Down Expand Up @@ -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
}

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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
}
}
}
39 changes: 39 additions & 0 deletions Tests/CodableToTypeScriptTests/Generate/GenerateEnumTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

デフォルトの変換処理が EnumConverter を採用した場合に、
モードを変更した新しい EnumConverter に差し替える


try assertGenerate(
source: """
enum E {}
""",
typeConverterProvider: typeConverterProvider,
expecteds: [
"""
export type E = void & TagRecord<"E">;
"""
]
)
}
}
Loading