From 9eb850c15356b045ac64a654fee1dc27fa58e900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Fri, 10 May 2024 14:06:36 +0800 Subject: [PATCH 1/8] Add support to Type Union --- demo/basic/generated/kotlin/BridgeTypes.kt | 45 ++++++++++++ demo/basic/generated/kotlin/IHtmlApi.kt | 8 +- demo/basic/generated/swift/IHtmlApi.swift | 4 +- demo/basic/generated/swift/SharedTypes.swift | 48 +++++++++++- demo/basic/interfaces.ts | 1 + documentation/interface-guide.md | 6 +- example-templates/kotlin-named-type.mustache | 54 ++++++++++++++ example-templates/swift-named-type.mustache | 34 +++++++++ src/generator/CodeGenerator.ts | 9 ++- src/generator/named-types.ts | 33 ++++++++- src/parser/ValueParser.ts | 73 ++++++++++++------- .../KotlinValueTransformer.ts | 5 ++ .../SwiftValueTransformer.ts | 5 ++ src/renderer/views/TypeUnionView.ts | 53 ++++++++++++++ src/renderer/views/index.ts | 4 +- src/serializers.ts | 42 +++++++---- src/types.ts | 18 ++++- 17 files changed, 383 insertions(+), 59 deletions(-) create mode 100644 src/renderer/views/TypeUnionView.ts diff --git a/demo/basic/generated/kotlin/BridgeTypes.kt b/demo/basic/generated/kotlin/BridgeTypes.kt index 58d0c2d..b09004c 100644 --- a/demo/basic/generated/kotlin/BridgeTypes.kt +++ b/demo/basic/generated/kotlin/BridgeTypes.kt @@ -30,6 +30,7 @@ data class OverriddenFullSize( @JvmField val nullableStringUnion: OverriddenFullSizeMembersNullableStringUnionType?, @JvmField val numUnion1: OverriddenFullSizeMembersNumUnion1Type, @JvmField val foo: OverriddenFullSizeMembersFooType, + @JvmField val typeUnion: OverriddenFullSizeMembersTypeUnionType, @JvmField val width: Float, @JvmField val height: Float, @JvmField val scale: Float, @@ -117,3 +118,47 @@ data class OverriddenFullSizeMembersFooType( @JvmField val stringField: String, @JvmField val numberField: Float, ) + +sealed class OverriddenFullSizeMembersTypeUnionType(val value: Any) { + data class StringValue(val value: String) : OverriddenFullSizeMembersTypeUnionType() + data class FloatValue(val value: Float) : OverriddenFullSizeMembersTypeUnionType() + data class BooleanValue(val value: Boolean) : OverriddenFullSizeMembersTypeUnionType() + data class NumEnumValue(val value: NumEnum) : OverriddenFullSizeMembersTypeUnionType() + data class DefaultEnumValue(val value: DefaultEnum) : OverriddenFullSizeMembersTypeUnionType() +} + +class OverriddenFullSizeMembersTypeUnionTypeAdapter : JsonSerializer, JsonDeserializer { + override fun serialize(src: OverriddenFullSizeMembersTypeUnionType, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + return context.serialize(src.value) + } + + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): OverriddenFullSizeMembersTypeUnionType { + when { + json.isJsonPrimitive -> { + val primitive = json.asJsonPrimitive + if (primitive.isString) { + return OverriddenFullSizeMembersTypeUnionType.StringValue(primitive.asString) + } + if (primitive.isNumber) { + return OverriddenFullSizeMembersTypeUnionType.FloatValue(primitive.asFloat) + } + if (primitive.isBoolean) { + return OverriddenFullSizeMembersTypeUnionType.BooleanValue(primitive.asBoolean) + } + } + json.isJsonObject -> { + try { + return OverriddenFullSizeMembersTypeUnionType.NumEnumValue(context.deserialize(json, NumEnum::class.java)) + } catch (e: Exception) { + // Ignore the exception and try the next type + } + try { + return OverriddenFullSizeMembersTypeUnionType.DefaultEnumValue(context.deserialize(json, DefaultEnum::class.java)) + } catch (e: Exception) { + // Ignore the exception and try the next type + } + } + else -> throw IllegalArgumentException("Unexpected JSON type: ${json.javaClass}") + } + } +} diff --git a/demo/basic/generated/kotlin/IHtmlApi.kt b/demo/basic/generated/kotlin/IHtmlApi.kt index 7ca0177..33d6544 100644 --- a/demo/basic/generated/kotlin/IHtmlApi.kt +++ b/demo/basic/generated/kotlin/IHtmlApi.kt @@ -33,7 +33,7 @@ interface IHtmlApiBridge { fun getName(callback: Callback) fun getAge(gender: IHtmlApiGetAgeGender, callback: Callback) fun testDictionaryWithAnyKey(dict: Map) - fun testDefaultValue(bool: Boolean? = null, bool2: Boolean?, bool3: Boolean = true, num: Float = 1, string: String = "hello", callback: Callback) + fun testDefaultValue(bool: Boolean? = null, bool2: Boolean?, bool3: Boolean = true, num: Float = 1, string: String = "hello", callback: Callback) } open class IHtmlApiBridge(editor: WebEditor, gson: Gson) : JsBridge(editor, gson, "htmlApi"), IHtmlApiBridge { @@ -88,8 +88,8 @@ open class IHtmlApiBridge(editor: WebEditor, gson: Gson) : JsBridge(editor, gson )) } - override fun testDefaultValue(bool: Boolean? = null, bool2: Boolean?, bool3: Boolean = true, num: Float = 1, string: String = "hello", callback: Callback) { - executeJsForResponse(nterfaceWithDefeaultValue::class.java, "testDefaultValue", callback, mapOf( + override fun testDefaultValue(bool: Boolean? = null, bool2: Boolean?, bool3: Boolean = true, num: Float = 1, string: String = "hello", callback: Callback) { + executeJsForResponse(ObjectWithDefeaultValue::class.java, "testDefaultValue", callback, mapOf( "bool" to bool "bool2" to bool2 "bool3" to bool3 @@ -133,6 +133,6 @@ class IHtmlApiGetAgeReturnTypeTypeAdapter : JsonSerializer) { + public func testDefaultValue(bool: Bool? = nil, bool2: Bool?, bool3: Bool = true, num: Double = 1, string: String = "hello", completion: @escaping BridgeCompletion) { struct Args: Encodable { let bool: Bool? let bool2: Bool? @@ -130,7 +130,7 @@ public enum IHtmlApiGetAgeReturnType: Int, Codable { case _22 = 22 } -public struct nterfaceWithDefeaultValue: Codable { +public struct ObjectWithDefeaultValue: Codable { public var defaultValue: Bool? public init(defaultValue: Bool? = true) { diff --git a/demo/basic/generated/swift/SharedTypes.swift b/demo/basic/generated/swift/SharedTypes.swift index 1c329a0..064da4b 100644 --- a/demo/basic/generated/swift/SharedTypes.swift +++ b/demo/basic/generated/swift/SharedTypes.swift @@ -19,13 +19,14 @@ public struct OverriddenFullSize: Codable { public var nullableStringUnion: OverriddenFullSizeMembersNullableStringUnionType? public var numUnion1: OverriddenFullSizeMembersNumUnion1Type public var foo: OverriddenFullSizeMembersFooType + public var typeUnion: OverriddenFullSizeMembersTypeUnionType public var width: Double public var height: Double public var scale: Double /// Example documentation for member private var member: NumEnum = .one - public init(size: Double, count: Int, stringEnum: StringEnum, numEnum: NumEnum, defEnum: DefaultEnum, stringUnion: OverriddenFullSizeMembersStringUnionType, numberStringUnion: OverriddenFullSizeMembersNumberStringUnionType, nullableStringUnion: OverriddenFullSizeMembersNullableStringUnionType?, numUnion1: OverriddenFullSizeMembersNumUnion1Type, foo: OverriddenFullSizeMembersFooType, width: Double, height: Double, scale: Double) { + public init(size: Double, count: Int, stringEnum: StringEnum, numEnum: NumEnum, defEnum: DefaultEnum, stringUnion: OverriddenFullSizeMembersStringUnionType, numberStringUnion: OverriddenFullSizeMembersNumberStringUnionType, nullableStringUnion: OverriddenFullSizeMembersNullableStringUnionType?, numUnion1: OverriddenFullSizeMembersNumUnion1Type, foo: OverriddenFullSizeMembersFooType, typeUnion: OverriddenFullSizeMembersTypeUnionType, width: Double, height: Double, scale: Double) { self.size = size self.count = count self.stringEnum = stringEnum @@ -36,6 +37,7 @@ public struct OverriddenFullSize: Codable { self.nullableStringUnion = nullableStringUnion self.numUnion1 = numUnion1 self.foo = foo + self.typeUnion = typeUnion self.width = width self.height = height self.scale = scale @@ -87,3 +89,47 @@ public struct OverriddenFullSizeMembersFooType: Codable { self.numberField = numberField } } + +public enum OverriddenFullSizeMembersTypeUnionType: Codable { + case string(_ value: String) + case double(_ value: Double) + case bool(_ value: Bool) + case numEnum(_ value: NumEnum) + case defaultEnum(_ value: DefaultEnum) + + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + if let value = try? container.decode(String.self) { + self = .string(value) + } + else if let value = try? container.decode(Double.self) { + self = .double(value) + } + else if let value = try? container.decode(Bool.self) { + self = .bool(value) + } + else if let value = try? container.decode(NumEnum.self) { + self = .numEnum(value) + } + else { + let value = try container.decode(DefaultEnum.self) + self = .defaultEnum(value) + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .string(let value): + try container.encode(value) + case .double(let value): + try container.encode(value) + case .bool(let value): + try container.encode(value) + case .numEnum(let value): + try container.encode(value) + case .defaultEnum(let value): + try container.encode(value) + } + } +} diff --git a/demo/basic/interfaces.ts b/demo/basic/interfaces.ts index ba66c75..7518e39 100644 --- a/demo/basic/interfaces.ts +++ b/demo/basic/interfaces.ts @@ -49,6 +49,7 @@ interface FullSize extends BaseSize, CustomSize { nullableStringUnion: 'A1' | 'B1' | null; numUnion1: 11 | 21; foo: { stringField: string } | { numberField: number }; + typeUnion: string | number | boolean | NumEnum | DefaultEnum; } interface DictionaryWithAnyKey { diff --git a/documentation/interface-guide.md b/documentation/interface-guide.md index 27b74c4..537f1ba 100644 --- a/documentation/interface-guide.md +++ b/documentation/interface-guide.md @@ -176,8 +176,10 @@ interface NumberFieldInterface { StringFieldInterface | { numberField: number } StringFieldInterface | NumberFieldInterface -// not allowed: unsupported union +// allowed: types union string | number + +// not allowed: mixing type and tuple { stringField: string } | number ``` @@ -216,7 +218,7 @@ ts-gyb parses tags in [JSDoc](https://jsdoc.app) documentation. - `@shouldExport`: Specify whether an `interface` should be exported. Set it to `true` to export. - `@overrideModuleName`: Change the name of the interface for ts-gyb. This is helpful for dropping the `I` prefix in TypeScript interface name. - `@overrideTypeName`: Similar to `@overrideModuleName`, this is used to override the name of custom types used in method parameters or return values. -- `@default`: default value for Module Interface's function parameter, +- `@default`: default value for Module Interface's function parameter, ```typescript /** diff --git a/example-templates/kotlin-named-type.mustache b/example-templates/kotlin-named-type.mustache index 96606bb..002aac4 100644 --- a/example-templates/kotlin-named-type.mustache +++ b/example-templates/kotlin-named-type.mustache @@ -38,3 +38,57 @@ enum class {{typeName}} { } {{/isStringType}} {{/enum}} +{{#typeUnion}} +sealed class {{typeName}}(val value: Any) { + {{#members}} + data class {{type}}Value(val value: {{{type}}}) : {{typeName}}() + {{/members}} +} + +class {{typeName}}Adapter : JsonSerializer<{{typeName}}>, JsonDeserializer<{{typeName}}> { + override fun serialize(src: {{typeName}}, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + return context.serialize(src.value) + } + + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): {{typeName}} { + when { + {{#hasBasicType}} + json.isJsonPrimitive -> { + val primitive = json.asJsonPrimitive + {{#members}} + {{#isNumber}} + if (primitive.isNumber) { + return {{typeName}}.{{type}}Value(primitive.asFloat) + } + {{/isNumber}} + {{#isBoolean}} + if (primitive.isBoolean) { + return {{typeName}}.{{type}}Value(primitive.asBoolean) + } + {{/isBoolean}} + {{#isString}} + if (primitive.isString) { + return {{typeName}}.{{type}}Value(primitive.asString) + } + {{/isString}} + {{/members}} + } + {{/hasBasicType}} + {{#hasTupleType}} + json.isJsonObject -> { + {{#members}} + {{#isTuple}} + try { + return {{typeName}}.{{type}}Value(context.deserialize(json, {{type}}::class.java)) + } catch (e: Exception) { + // Ignore the exception and try the next type + } + {{/isTuple}} + {{/members}} + } + {{/hasTupleType}} + else -> throw IllegalArgumentException("Unexpected JSON type: ${json.javaClass}") + } + } +} +{{/typeUnion}} \ No newline at end of file diff --git a/example-templates/swift-named-type.mustache b/example-templates/swift-named-type.mustache index be2507b..0128f53 100644 --- a/example-templates/swift-named-type.mustache +++ b/example-templates/swift-named-type.mustache @@ -36,3 +36,37 @@ public enum {{typeName}}: {{valueType}}, Codable { {{/members}} } {{/enum}} +{{#typeUnion}} +public enum {{typeName}}: Codable { + {{#members}} + case {{name}}(_ value: {{type}}) + {{/members}} + + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + {{#members}} + {{^last}} + {{^first}}else {{/first}}if let value = try? container.decode({{type}}.self) { + self = .{{name}}(value) + } + {{/last}} + {{#last}} + else { + let value = try container.decode({{type}}.self) + self = .{{name}}(value) + } + {{/last}} + {{/members}} + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + {{#members}} + case .{{name}}(let value): + try container.encode(value) + {{/members}} + } + } +} +{{/typeUnion}} \ No newline at end of file diff --git a/src/generator/CodeGenerator.ts b/src/generator/CodeGenerator.ts index 0e8c937..3b081d0 100644 --- a/src/generator/CodeGenerator.ts +++ b/src/generator/CodeGenerator.ts @@ -10,9 +10,9 @@ import { } from './named-types'; import { Parser } from '../parser/Parser'; import { renderCode } from '../renderer/renderer'; -import { NamedTypeView, ModuleView, InterfaceTypeView, EnumTypeView } from '../renderer/views'; +import { NamedTypeView, ModuleView, InterfaceTypeView, EnumTypeView, TypeUnionView } from '../renderer/views'; import { serializeModule, serializeNamedType } from '../serializers'; -import { isInterfaceType } from '../types'; +import { isEnumType, isInterfaceType } from '../types'; import { applyDefaultCustomTags } from './utils'; import { ValueTransformer, SwiftValueTransformer, KotlinValueTransformer } from '../renderer/value-transformer'; @@ -128,9 +128,12 @@ export class CodeGenerator { if (isInterfaceType(namedType.type)) { namedTypeView = new InterfaceTypeView(namedType.type, namedType.source, valueTransformer); namedTypeView.custom = true; - } else { + } else if (isEnumType(namedType.type)) { namedTypeView = new EnumTypeView(namedType.type, namedType.source, valueTransformer); namedTypeView.enum = true; + } else { + namedTypeView = new TypeUnionView(namedType.type, valueTransformer); + namedTypeView.typeUnion = true; } return namedTypeView; diff --git a/src/generator/named-types.ts b/src/generator/named-types.ts index df9e82b..2e65907 100644 --- a/src/generator/named-types.ts +++ b/src/generator/named-types.ts @@ -1,5 +1,6 @@ import { basicTypeOfUnion, + capitalize, membersOfUnion, uniquePathWithMember, uniquePathWithMethodParameter, @@ -21,6 +22,10 @@ import { isUnionType, EnumSubType, UnionType, + isTypeUnion, + isBasicType, + isPredefinedType, + TypeUnion, } from '../types'; export const enum ValueTypeSource { @@ -29,7 +34,7 @@ export const enum ValueTypeSource { Return = 1 << 2, } -export type NamedType = InterfaceType | EnumType; +export type NamedType = InterfaceType | EnumType | TypeUnion; export interface NamedTypeInfo { type: NamedType; source: ValueTypeSource; @@ -132,6 +137,8 @@ function fetchNamedTypes(modules: Module[]): NamedTypesResult { namedType.members = members; namedType.documentation = ''; namedType.customTags = {}; + } else if (isTypeUnion(namedType)) { + namedType.name = path; } if (typeMap[namedType.name] === undefined) { @@ -197,7 +204,7 @@ function fetchRootTypes(module: Module): { valueType: ValueType; source: ValueTy function recursiveVisitMembersType( valueType: ValueType, - visit: (membersType: NamedType | TupleType | UnionType, path: string) => void, + visit: (membersType: NamedType | TupleType | UnionType | TypeUnion, path: string) => void, path: string ): void { if (isInterfaceType(valueType)) { @@ -245,13 +252,31 @@ function recursiveVisitMembersType( return; } - if (valueType.kind === ValueTypeKind.basicType) { + if (isTypeUnion(valueType)) { + visit(valueType, path); + valueType.members.forEach((member) => { + let subType: string; + if (isBasicType(member)) { + subType = member.value; + } else if ((member as NamedType).name !== undefined) { + subType = (member as NamedType).name; + } else { + subType = member.kind; + } + recursiveVisitMembersType(member, visit, `${path}${capitalize(subType)}`); + }); + return; + } + + if (isBasicType(valueType)) { // string, boolean, etc. return; } - if (valueType.kind === ValueTypeKind.predefinedType) { + + if (isPredefinedType(valueType)) { // CodeGen_Int, etc. return; } + throw Error(`Unhandled value type ${JSON.stringify(valueType)}`); } diff --git a/src/parser/ValueParser.ts b/src/parser/ValueParser.ts index 298b074..c071c71 100644 --- a/src/parser/ValueParser.ts +++ b/src/parser/ValueParser.ts @@ -267,7 +267,9 @@ export class ValueParser { } let nullable = false; - let valueType: ValueType | undefined; + let isTuple = false; + const valueTypes: ValueType[] = []; + const tupleMembers: Field[] = []; const literalValues: { type: BasicTypeValue.string | BasicTypeValue.number; value: Value; @@ -295,28 +297,22 @@ export class ValueParser { const newValueType = this.valueTypeFromTypeNode(typeNode); - if (!valueType) { - valueType = newValueType; - return; + if (valueTypes.length === 0) { + isTuple = isInterfaceType(newValueType) || isTupleType(newValueType); } - if ( - (!isInterfaceType(valueType) && !isTupleType(valueType)) || - (!isInterfaceType(newValueType) && !isTupleType(newValueType)) - ) { - throw new ValueParserError( - `union type ${node.getText()} is invalid`, - 'Do not support multiple union types except for interface or literal type' - ); + if (isTuple) { + if (isInterfaceType(newValueType) || isTupleType(newValueType)) { + tupleMembers.push(...newValueType.members); + } else { + throw new ValueParserError( + `type ${node.getText()} is invalid`, + 'Use `multiple tuple types` or `union value types` or `type union`' + ); + } + } else { + valueTypes.push(newValueType); } - - const existingMemberNames = new Set(valueType.members.map((member) => member.name)); - valueType = { - kind: ValueTypeKind.tupleType, - members: valueType.members.concat( - newValueType.members.filter((member) => !existingMemberNames.has(member.name)) - ), - }; }); if (literalValues.length > 0) { @@ -351,21 +347,44 @@ export class ValueParser { } return unionKind; } - if (!valueType) { + + if (valueTypes.length === 0 && tupleMembers.length === 0) { + throw new ValueParserError( + `type ${node.getText()} is invalid`, + 'Type must contain one supported non empty type' + ); + } + + if (valueTypes.length > 0 && tupleMembers.length > 0) { throw new ValueParserError( - `union type ${node.getText()} is invalid`, - 'Union type must contain one supported non empty type' + `mixing ${node.getText()} is invalid`, + 'Type must contain types or tuples' ); } - if (!isOptionalType(valueType) && nullable) { + if (isTuple) { return { - kind: ValueTypeKind.optionalType, - wrappedType: valueType, + kind: ValueTypeKind.tupleType, + members: tupleMembers, }; } - return valueType; + if (valueTypes.length === 1) { + if (!isOptionalType(valueTypes[0]) && nullable) { + return { + kind: ValueTypeKind.optionalType, + wrappedType: valueTypes[0], + }; + } + return valueTypes[0]; + } + + return { + name: '', + kind: ValueTypeKind.typeUnion, + members: valueTypes, + customTags: {}, + }; } private basicTypeKindFromTypeNode(node: ts.TypeNode): BasicType | null { diff --git a/src/renderer/value-transformer/KotlinValueTransformer.ts b/src/renderer/value-transformer/KotlinValueTransformer.ts index e8214fc..0e60f00 100644 --- a/src/renderer/value-transformer/KotlinValueTransformer.ts +++ b/src/renderer/value-transformer/KotlinValueTransformer.ts @@ -10,6 +10,7 @@ import { isPredefinedType, ValueType, Value, + isTypeUnion, } from '../../types'; import { ValueTransformer } from './ValueTransformer'; @@ -66,6 +67,10 @@ export class KotlinValueTransformer implements ValueTransformer { return this.typeNameMap[valueType.name] ?? valueType.name; } + if (isTypeUnion(valueType)) { + return this.convertTypeNameFromCustomMap(valueType.name); + } + throw Error('Type not handled'); } diff --git a/src/renderer/value-transformer/SwiftValueTransformer.ts b/src/renderer/value-transformer/SwiftValueTransformer.ts index 941e8e6..0ba9de3 100644 --- a/src/renderer/value-transformer/SwiftValueTransformer.ts +++ b/src/renderer/value-transformer/SwiftValueTransformer.ts @@ -10,6 +10,7 @@ import { isPredefinedType, ValueType, Value, + isTypeUnion, } from '../../types'; import { ValueTransformer } from './ValueTransformer'; @@ -66,6 +67,10 @@ export class SwiftValueTransformer implements ValueTransformer { return this.typeNameMap[valueType.name] ?? valueType.name; } + if (isTypeUnion(valueType)) { + return this.convertTypeNameFromCustomMap(valueType.name); + } + throw Error('Type not handled'); } diff --git a/src/renderer/views/TypeUnionView.ts b/src/renderer/views/TypeUnionView.ts new file mode 100644 index 0000000..73d8808 --- /dev/null +++ b/src/renderer/views/TypeUnionView.ts @@ -0,0 +1,53 @@ +import { uncapitalize } from "../../utils"; +import { TypeUnion, isBasicType } from '../../types'; +import { ValueTransformer } from '../value-transformer'; + +export class TypeUnionView { + constructor( + private readonly value: TypeUnion, + private readonly valueTransformer: ValueTransformer + ) { } + + get typeName(): string { + return this.valueTransformer.convertTypeNameFromCustomMap(this.value.name); + } + + get hasBasicType(): boolean { + const { members } = this.value; + + return members.filter(isBasicType).length > 0; + } + + get hasTupleType(): boolean { + const { members } = this.value; + + return members.filter(isBasicType).length !== members.length; + } + + get members(): { + name: string, + type: string; + first: boolean; + last: boolean; + isTuple: boolean; + isNumber: boolean; + isBoolean: boolean; + isString: boolean; + }[] { + const { members } = this.value; + + return members.map((member, index) => { + const typeName = this.valueTransformer.convertValueType(member); + return { + name: uncapitalize(typeName), + type: typeName, + first: index === 0, + last: index === members.length - 1, + isTuple: !isBasicType(member), + isNumber: isBasicType(member) && member.value === 'number', + isBoolean: isBasicType(member) && member.value === 'boolean', + isString: isBasicType(member) && member.value === 'string', + }; + }); + } +} diff --git a/src/renderer/views/index.ts b/src/renderer/views/index.ts index be266ec..abd24b9 100644 --- a/src/renderer/views/index.ts +++ b/src/renderer/views/index.ts @@ -1,9 +1,11 @@ import { InterfaceTypeView } from './InterfaceTypeView'; import { EnumTypeView } from './EnumTypeView'; +import { TypeUnionView } from './TypeUnionView'; export * from './EnumTypeView'; export * from './InterfaceTypeView'; export * from './MethodView'; export * from './ModuleView'; +export * from './TypeUnionView'; -export type NamedTypeView = (InterfaceTypeView | EnumTypeView) & { custom?: boolean; enum?: boolean }; +export type NamedTypeView = (InterfaceTypeView | EnumTypeView | TypeUnionView) & { custom?: boolean; enum?: boolean; typeUnion?: boolean; }; diff --git a/src/serializers.ts b/src/serializers.ts index 7ee1dea..68d222b 100644 --- a/src/serializers.ts +++ b/src/serializers.ts @@ -13,6 +13,7 @@ import { Module, ValueType, Value, + isTypeUnion, } from './types'; const keywordColor = chalk.green; @@ -71,20 +72,31 @@ ${namedType.members .join('\n')} }`; } + if (isEnumType(namedType)) { + return `${serializeDocumentation(namedType.documentation)}${documentationColor(customTags)}${keywordColor('Enum')} ${namedType.name + } { + ${namedType.members + .map( + (member) => + `${serializeDocumentation(member.documentation)}${identifierColor(member.key)} = ${valueColor(member.value)}` + ) + .join('\n') + .split('\n') + .map((line) => ` ${line}`) + .join('\n')} + }`; + } + if (isTypeUnion(namedType)) { + return `${documentationColor(customTags)} + ${namedType.members + .map( + (member) => + serializeValueType(member) + ) + .join(' | ')}`; + } - return `${serializeDocumentation(namedType.documentation)}${documentationColor(customTags)}${keywordColor('Enum')} ${ - namedType.name - } { -${namedType.members - .map( - (member) => - `${serializeDocumentation(member.documentation)}${identifierColor(member.key)} = ${valueColor(member.value)}` - ) - .join('\n') - .split('\n') - .map((line) => ` ${line}`) - .join('\n')} -}`; + throw Error(`Unhandled value type ${JSON.stringify(namedType)}`); } function serializeMethod(method: Method): string { @@ -126,6 +138,10 @@ function serializeValueType(valueType: ValueType): string { return valueType.name; } + if (isTypeUnion(valueType)) { + return valueType.name; + } + throw Error(`Unhandled value type ${JSON.stringify(valueType)}`); } diff --git a/src/types.ts b/src/types.ts index f535e67..946a72c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,5 @@ +import { Type } from "typescript"; + export interface Module { name: string; members: Field[]; @@ -32,7 +34,8 @@ export type NonEmptyType = | ArrayType | DictionaryType | PredefinedType - | UnionType; + | UnionType + | TypeUnion; export enum ValueTypeKind { basicType = 'basicType', @@ -44,6 +47,7 @@ export enum ValueTypeKind { optionalType = 'optionalType', predefinedType = 'predefinedType', unionType = 'unionType', + typeUnion = 'typeUnion', } interface BaseValueType { @@ -60,7 +64,6 @@ export interface BasicType extends BaseValueType { kind: ValueTypeKind.basicType; value: BasicTypeValue; } - export interface InterfaceType extends BaseValueType, Omit { kind: ValueTypeKind.interfaceType; } @@ -124,6 +127,13 @@ export interface UnionType extends BaseValueType { members: UnionLiteralType[]; } +export interface TypeUnion extends BaseValueType { + name: string; + kind: ValueTypeKind.typeUnion; + members: ValueType[]; + customTags: Record; +} + export function isBasicType(valueType: ValueType): valueType is BasicType { return valueType.kind === ValueTypeKind.basicType; } @@ -160,6 +170,10 @@ export function isUnionType(valueType: ValueType): valueType is UnionType { return valueType.kind === ValueTypeKind.unionType; } +export function isTypeUnion(valueType: ValueType): valueType is TypeUnion { + return valueType.kind === ValueTypeKind.typeUnion; +} + // TODO: Define these types to support recursive definition type BaseValue = string | number | boolean | Record | null; export type Value = BaseValue | BaseValue[] | Record; From 4bcced1cc3d16310a67c18d4968beeb57d3b5d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Fri, 10 May 2024 14:07:40 +0800 Subject: [PATCH 2/8] bump version to 0.12.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6090296..9ece0e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ts-gyb", - "version": "0.11.1", + "version": "0.12.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ts-gyb", - "version": "0.11.1", + "version": "0.12.0", "license": "MIT", "dependencies": { "chalk": "^4.1.1", diff --git a/package.json b/package.json index c2327fd..225306b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ts-gyb", - "version": "0.11.1", + "version": "0.12.0", "description": "Generate Native API based on TS interface", "repository": { "type": "git", From cdebdc96b3acc997c9fccea07d1428add76e4506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Fri, 10 May 2024 14:31:22 +0800 Subject: [PATCH 3/8] fix test fail --- src/parser/ValueParser.ts | 10 +++++++++- test/value-parser-test.ts | 18 ++++++++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/parser/ValueParser.ts b/src/parser/ValueParser.ts index c071c71..9cc9888 100644 --- a/src/parser/ValueParser.ts +++ b/src/parser/ValueParser.ts @@ -363,10 +363,18 @@ export class ValueParser { } if (isTuple) { - return { + const value: TupleType = { kind: ValueTypeKind.tupleType, members: tupleMembers, }; + if (nullable) { + const optionalType: OptionalType = { + kind: ValueTypeKind.optionalType, + wrappedType: value, + }; + return optionalType; + } + return value; } if (valueTypes.length === 1) { diff --git a/test/value-parser-test.ts b/test/value-parser-test.ts index b8c6988..a4d1036 100644 --- a/test/value-parser-test.ts +++ b/test/value-parser-test.ts @@ -289,15 +289,21 @@ describe('ValueParser', () => { it('Empty types union', () => { const valueTypeCode = 'null | undefined'; withTempValueParser(valueTypeCode, parseFunc => { - expect(parseFunc).to.throw('union type null | undefined is invalid'); + expect(parseFunc).to.throw('type null | undefined is invalid'); }); }); - it('Multiple types union', () => { - const valueTypeCode = 'string | number'; - withTempValueParser(valueTypeCode, parseFunc => { - expect(parseFunc).to.throw('union type string | number is invalid'); - }); + testValueType('Multiple types union', 'string | number', { + customTags: {}, + name: '', + kind: ValueTypeKind.typeUnion, + members: [{ + kind: ValueTypeKind.basicType, + value: BasicTypeValue.string, + }, { + kind: ValueTypeKind.basicType, + value: BasicTypeValue.number, + }], }); const optionalStringType: OptionalType = { kind: ValueTypeKind.optionalType, wrappedType: stringType }; From e07ea0bf028ed51057b783c8c63f767f2529ec52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Fri, 10 May 2024 14:52:06 +0800 Subject: [PATCH 4/8] fix lint --- src/types.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/types.ts b/src/types.ts index 946a72c..e9ec736 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,3 @@ -import { Type } from "typescript"; - export interface Module { name: string; members: Field[]; From c2ffc96398a86d7419066e83ebae59b8318843c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Fri, 10 May 2024 15:41:23 +0800 Subject: [PATCH 5/8] Rename UnionType to LiteralType & let UnionType become new supported feature --- demo/basic/generated/kotlin/BridgeTypes.kt | 30 +++++++++---------- demo/basic/generated/swift/SharedTypes.swift | 8 ++--- demo/basic/interfaces.ts | 2 +- example-templates/kotlin-named-type.mustache | 4 +-- example-templates/swift-named-type.mustache | 4 +-- src/generator/CodeGenerator.ts | 6 ++-- src/generator/named-types.ts | 20 ++++++------- src/parser/ValueParser.ts | 20 ++++++------- .../KotlinValueTransformer.ts | 4 +-- .../SwiftValueTransformer.ts | 4 +-- .../{TypeUnionView.ts => UnionTypeView.ts} | 6 ++-- src/renderer/views/index.ts | 6 ++-- src/serializers.ts | 6 ++-- src/types.ts | 22 +++++++------- src/utils.ts | 6 ++-- test/value-parser-test.ts | 2 +- 16 files changed, 75 insertions(+), 75 deletions(-) rename src/renderer/views/{TypeUnionView.ts => UnionTypeView.ts} (91%) diff --git a/demo/basic/generated/kotlin/BridgeTypes.kt b/demo/basic/generated/kotlin/BridgeTypes.kt index b09004c..6448607 100644 --- a/demo/basic/generated/kotlin/BridgeTypes.kt +++ b/demo/basic/generated/kotlin/BridgeTypes.kt @@ -30,7 +30,7 @@ data class OverriddenFullSize( @JvmField val nullableStringUnion: OverriddenFullSizeMembersNullableStringUnionType?, @JvmField val numUnion1: OverriddenFullSizeMembersNumUnion1Type, @JvmField val foo: OverriddenFullSizeMembersFooType, - @JvmField val typeUnion: OverriddenFullSizeMembersTypeUnionType, + @JvmField val unionType: OverriddenFullSizeMembersUnionTypeType, @JvmField val width: Float, @JvmField val height: Float, @JvmField val scale: Float, @@ -119,41 +119,41 @@ data class OverriddenFullSizeMembersFooType( @JvmField val numberField: Float, ) -sealed class OverriddenFullSizeMembersTypeUnionType(val value: Any) { - data class StringValue(val value: String) : OverriddenFullSizeMembersTypeUnionType() - data class FloatValue(val value: Float) : OverriddenFullSizeMembersTypeUnionType() - data class BooleanValue(val value: Boolean) : OverriddenFullSizeMembersTypeUnionType() - data class NumEnumValue(val value: NumEnum) : OverriddenFullSizeMembersTypeUnionType() - data class DefaultEnumValue(val value: DefaultEnum) : OverriddenFullSizeMembersTypeUnionType() +sealed class OverriddenFullSizeMembersUnionTypeType(val value: Any) { + data class StringValue(val value: String) : OverriddenFullSizeMembersUnionTypeType() + data class FloatValue(val value: Float) : OverriddenFullSizeMembersUnionTypeType() + data class BooleanValue(val value: Boolean) : OverriddenFullSizeMembersUnionTypeType() + data class NumEnumValue(val value: NumEnum) : OverriddenFullSizeMembersUnionTypeType() + data class DefaultEnumValue(val value: DefaultEnum) : OverriddenFullSizeMembersUnionTypeType() } -class OverriddenFullSizeMembersTypeUnionTypeAdapter : JsonSerializer, JsonDeserializer { - override fun serialize(src: OverriddenFullSizeMembersTypeUnionType, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { +class OverriddenFullSizeMembersUnionTypeTypeAdapter : JsonSerializer, JsonDeserializer { + override fun serialize(src: OverriddenFullSizeMembersUnionTypeType, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { return context.serialize(src.value) } - override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): OverriddenFullSizeMembersTypeUnionType { + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): OverriddenFullSizeMembersUnionTypeType { when { json.isJsonPrimitive -> { val primitive = json.asJsonPrimitive if (primitive.isString) { - return OverriddenFullSizeMembersTypeUnionType.StringValue(primitive.asString) + return OverriddenFullSizeMembersUnionTypeType.StringValue(primitive.asString) } if (primitive.isNumber) { - return OverriddenFullSizeMembersTypeUnionType.FloatValue(primitive.asFloat) + return OverriddenFullSizeMembersUnionTypeType.FloatValue(primitive.asFloat) } if (primitive.isBoolean) { - return OverriddenFullSizeMembersTypeUnionType.BooleanValue(primitive.asBoolean) + return OverriddenFullSizeMembersUnionTypeType.BooleanValue(primitive.asBoolean) } } json.isJsonObject -> { try { - return OverriddenFullSizeMembersTypeUnionType.NumEnumValue(context.deserialize(json, NumEnum::class.java)) + return OverriddenFullSizeMembersUnionTypeType.NumEnumValue(context.deserialize(json, NumEnum::class.java)) } catch (e: Exception) { // Ignore the exception and try the next type } try { - return OverriddenFullSizeMembersTypeUnionType.DefaultEnumValue(context.deserialize(json, DefaultEnum::class.java)) + return OverriddenFullSizeMembersUnionTypeType.DefaultEnumValue(context.deserialize(json, DefaultEnum::class.java)) } catch (e: Exception) { // Ignore the exception and try the next type } diff --git a/demo/basic/generated/swift/SharedTypes.swift b/demo/basic/generated/swift/SharedTypes.swift index 064da4b..cca6d5d 100644 --- a/demo/basic/generated/swift/SharedTypes.swift +++ b/demo/basic/generated/swift/SharedTypes.swift @@ -19,14 +19,14 @@ public struct OverriddenFullSize: Codable { public var nullableStringUnion: OverriddenFullSizeMembersNullableStringUnionType? public var numUnion1: OverriddenFullSizeMembersNumUnion1Type public var foo: OverriddenFullSizeMembersFooType - public var typeUnion: OverriddenFullSizeMembersTypeUnionType + public var unionType: OverriddenFullSizeMembersUnionTypeType public var width: Double public var height: Double public var scale: Double /// Example documentation for member private var member: NumEnum = .one - public init(size: Double, count: Int, stringEnum: StringEnum, numEnum: NumEnum, defEnum: DefaultEnum, stringUnion: OverriddenFullSizeMembersStringUnionType, numberStringUnion: OverriddenFullSizeMembersNumberStringUnionType, nullableStringUnion: OverriddenFullSizeMembersNullableStringUnionType?, numUnion1: OverriddenFullSizeMembersNumUnion1Type, foo: OverriddenFullSizeMembersFooType, typeUnion: OverriddenFullSizeMembersTypeUnionType, width: Double, height: Double, scale: Double) { + public init(size: Double, count: Int, stringEnum: StringEnum, numEnum: NumEnum, defEnum: DefaultEnum, stringUnion: OverriddenFullSizeMembersStringUnionType, numberStringUnion: OverriddenFullSizeMembersNumberStringUnionType, nullableStringUnion: OverriddenFullSizeMembersNullableStringUnionType?, numUnion1: OverriddenFullSizeMembersNumUnion1Type, foo: OverriddenFullSizeMembersFooType, unionType: OverriddenFullSizeMembersUnionTypeType, width: Double, height: Double, scale: Double) { self.size = size self.count = count self.stringEnum = stringEnum @@ -37,7 +37,7 @@ public struct OverriddenFullSize: Codable { self.nullableStringUnion = nullableStringUnion self.numUnion1 = numUnion1 self.foo = foo - self.typeUnion = typeUnion + self.unionType = unionType self.width = width self.height = height self.scale = scale @@ -90,7 +90,7 @@ public struct OverriddenFullSizeMembersFooType: Codable { } } -public enum OverriddenFullSizeMembersTypeUnionType: Codable { +public enum OverriddenFullSizeMembersUnionTypeType: Codable { case string(_ value: String) case double(_ value: Double) case bool(_ value: Bool) diff --git a/demo/basic/interfaces.ts b/demo/basic/interfaces.ts index 7518e39..c0c8f7e 100644 --- a/demo/basic/interfaces.ts +++ b/demo/basic/interfaces.ts @@ -49,7 +49,7 @@ interface FullSize extends BaseSize, CustomSize { nullableStringUnion: 'A1' | 'B1' | null; numUnion1: 11 | 21; foo: { stringField: string } | { numberField: number }; - typeUnion: string | number | boolean | NumEnum | DefaultEnum; + unionType: string | number | boolean | NumEnum | DefaultEnum; } interface DictionaryWithAnyKey { diff --git a/example-templates/kotlin-named-type.mustache b/example-templates/kotlin-named-type.mustache index 002aac4..d7f2b0f 100644 --- a/example-templates/kotlin-named-type.mustache +++ b/example-templates/kotlin-named-type.mustache @@ -38,7 +38,7 @@ enum class {{typeName}} { } {{/isStringType}} {{/enum}} -{{#typeUnion}} +{{#unionType}} sealed class {{typeName}}(val value: Any) { {{#members}} data class {{type}}Value(val value: {{{type}}}) : {{typeName}}() @@ -91,4 +91,4 @@ class {{typeName}}Adapter : JsonSerializer<{{typeName}}>, JsonDeserializer<{{typ } } } -{{/typeUnion}} \ No newline at end of file +{{/unionType}} \ No newline at end of file diff --git a/example-templates/swift-named-type.mustache b/example-templates/swift-named-type.mustache index 0128f53..ae684a8 100644 --- a/example-templates/swift-named-type.mustache +++ b/example-templates/swift-named-type.mustache @@ -36,7 +36,7 @@ public enum {{typeName}}: {{valueType}}, Codable { {{/members}} } {{/enum}} -{{#typeUnion}} +{{#unionType}} public enum {{typeName}}: Codable { {{#members}} case {{name}}(_ value: {{type}}) @@ -69,4 +69,4 @@ public enum {{typeName}}: Codable { } } } -{{/typeUnion}} \ No newline at end of file +{{/unionType}} \ No newline at end of file diff --git a/src/generator/CodeGenerator.ts b/src/generator/CodeGenerator.ts index 3b081d0..cdf9a53 100644 --- a/src/generator/CodeGenerator.ts +++ b/src/generator/CodeGenerator.ts @@ -10,7 +10,7 @@ import { } from './named-types'; import { Parser } from '../parser/Parser'; import { renderCode } from '../renderer/renderer'; -import { NamedTypeView, ModuleView, InterfaceTypeView, EnumTypeView, TypeUnionView } from '../renderer/views'; +import { NamedTypeView, ModuleView, InterfaceTypeView, EnumTypeView, UnionTypeView } from '../renderer/views'; import { serializeModule, serializeNamedType } from '../serializers'; import { isEnumType, isInterfaceType } from '../types'; import { applyDefaultCustomTags } from './utils'; @@ -132,8 +132,8 @@ export class CodeGenerator { namedTypeView = new EnumTypeView(namedType.type, namedType.source, valueTransformer); namedTypeView.enum = true; } else { - namedTypeView = new TypeUnionView(namedType.type, valueTransformer); - namedTypeView.typeUnion = true; + namedTypeView = new UnionTypeView(namedType.type, valueTransformer); + namedTypeView.unionType = true; } return namedTypeView; diff --git a/src/generator/named-types.ts b/src/generator/named-types.ts index 2e65907..5d437f9 100644 --- a/src/generator/named-types.ts +++ b/src/generator/named-types.ts @@ -19,13 +19,13 @@ import { TupleType, isTupleType, ValueTypeKind, - isUnionType, + isLiteralType, EnumSubType, - UnionType, - isTypeUnion, + LiteralType, + isUnionType, isBasicType, isPredefinedType, - TypeUnion, + UnionType, } from '../types'; export const enum ValueTypeSource { @@ -34,7 +34,7 @@ export const enum ValueTypeSource { Return = 1 << 2, } -export type NamedType = InterfaceType | EnumType | TypeUnion; +export type NamedType = InterfaceType | EnumType | UnionType; export interface NamedTypeInfo { type: NamedType; source: ValueTypeSource; @@ -126,7 +126,7 @@ function fetchNamedTypes(modules: Module[]): NamedTypesResult { namedType.name = path; namedType.documentation = ''; namedType.customTags = {}; - } else if (isUnionType(namedType)) { + } else if (isLiteralType(namedType)) { const subType = basicTypeOfUnion(namedType); const members = membersOfUnion(namedType); @@ -137,7 +137,7 @@ function fetchNamedTypes(modules: Module[]): NamedTypesResult { namedType.members = members; namedType.documentation = ''; namedType.customTags = {}; - } else if (isTypeUnion(namedType)) { + } else if (isUnionType(namedType)) { namedType.name = path; } @@ -204,7 +204,7 @@ function fetchRootTypes(module: Module): { valueType: ValueType; source: ValueTy function recursiveVisitMembersType( valueType: ValueType, - visit: (membersType: NamedType | TupleType | UnionType | TypeUnion, path: string) => void, + visit: (membersType: NamedType | TupleType | LiteralType | UnionType, path: string) => void, path: string ): void { if (isInterfaceType(valueType)) { @@ -247,12 +247,12 @@ function recursiveVisitMembersType( return; } - if (isUnionType(valueType)) { + if (isLiteralType(valueType)) { visit(valueType, path); return; } - if (isTypeUnion(valueType)) { + if (isUnionType(valueType)) { visit(valueType, path); valueType.members.forEach((member) => { let subType: string; diff --git a/src/parser/ValueParser.ts b/src/parser/ValueParser.ts index 9cc9888..e3058e0 100644 --- a/src/parser/ValueParser.ts +++ b/src/parser/ValueParser.ts @@ -20,7 +20,7 @@ import { isTupleType, EnumField, isBasicType, - UnionType, + LiteralType, OptionalType, Value, UnionLiteralType, @@ -226,9 +226,9 @@ export class ValueParser { } private valueTypeFromTypeNode(typeNode: ts.TypeNode): ValueType { - const unionType = this.parseUnionTypeNode(typeNode); - if (unionType !== null) { - return unionType; + const literalType = this.parseLiteralOrUnionTypeNode(typeNode); + if (literalType !== null) { + return literalType; } const referenceType = this.parseReferenceTypeNode(typeNode); @@ -261,7 +261,7 @@ export class ValueParser { ); } - private parseUnionTypeNode(node: ts.TypeNode): ValueType | null { + private parseLiteralOrUnionTypeNode(node: ts.TypeNode): ValueType | null { if (!ts.isUnionTypeNode(node)) { return null; } @@ -333,19 +333,19 @@ export class ValueParser { members.push(obj.value); } }); - const unionKind: UnionType = { - kind: ValueTypeKind.unionType, + const literalKind: LiteralType = { + kind: ValueTypeKind.literalType, memberType: literalValues[0].type, members, }; if (nullable) { const optionalType: OptionalType = { kind: ValueTypeKind.optionalType, - wrappedType: unionKind, + wrappedType: literalKind, }; return optionalType; } - return unionKind; + return literalKind; } if (valueTypes.length === 0 && tupleMembers.length === 0) { @@ -389,7 +389,7 @@ export class ValueParser { return { name: '', - kind: ValueTypeKind.typeUnion, + kind: ValueTypeKind.unionType, members: valueTypes, customTags: {}, }; diff --git a/src/renderer/value-transformer/KotlinValueTransformer.ts b/src/renderer/value-transformer/KotlinValueTransformer.ts index 0e60f00..1202c1d 100644 --- a/src/renderer/value-transformer/KotlinValueTransformer.ts +++ b/src/renderer/value-transformer/KotlinValueTransformer.ts @@ -10,7 +10,7 @@ import { isPredefinedType, ValueType, Value, - isTypeUnion, + isUnionType, } from '../../types'; import { ValueTransformer } from './ValueTransformer'; @@ -67,7 +67,7 @@ export class KotlinValueTransformer implements ValueTransformer { return this.typeNameMap[valueType.name] ?? valueType.name; } - if (isTypeUnion(valueType)) { + if (isUnionType(valueType)) { return this.convertTypeNameFromCustomMap(valueType.name); } diff --git a/src/renderer/value-transformer/SwiftValueTransformer.ts b/src/renderer/value-transformer/SwiftValueTransformer.ts index 0ba9de3..f72df28 100644 --- a/src/renderer/value-transformer/SwiftValueTransformer.ts +++ b/src/renderer/value-transformer/SwiftValueTransformer.ts @@ -10,7 +10,7 @@ import { isPredefinedType, ValueType, Value, - isTypeUnion, + isUnionType, } from '../../types'; import { ValueTransformer } from './ValueTransformer'; @@ -67,7 +67,7 @@ export class SwiftValueTransformer implements ValueTransformer { return this.typeNameMap[valueType.name] ?? valueType.name; } - if (isTypeUnion(valueType)) { + if (isUnionType(valueType)) { return this.convertTypeNameFromCustomMap(valueType.name); } diff --git a/src/renderer/views/TypeUnionView.ts b/src/renderer/views/UnionTypeView.ts similarity index 91% rename from src/renderer/views/TypeUnionView.ts rename to src/renderer/views/UnionTypeView.ts index 73d8808..0ea0c03 100644 --- a/src/renderer/views/TypeUnionView.ts +++ b/src/renderer/views/UnionTypeView.ts @@ -1,10 +1,10 @@ import { uncapitalize } from "../../utils"; -import { TypeUnion, isBasicType } from '../../types'; +import { UnionType, isBasicType } from '../../types'; import { ValueTransformer } from '../value-transformer'; -export class TypeUnionView { +export class UnionTypeView { constructor( - private readonly value: TypeUnion, + private readonly value: UnionType, private readonly valueTransformer: ValueTransformer ) { } diff --git a/src/renderer/views/index.ts b/src/renderer/views/index.ts index abd24b9..c1611f5 100644 --- a/src/renderer/views/index.ts +++ b/src/renderer/views/index.ts @@ -1,11 +1,11 @@ import { InterfaceTypeView } from './InterfaceTypeView'; import { EnumTypeView } from './EnumTypeView'; -import { TypeUnionView } from './TypeUnionView'; +import { UnionTypeView } from './UnionTypeView'; export * from './EnumTypeView'; export * from './InterfaceTypeView'; export * from './MethodView'; export * from './ModuleView'; -export * from './TypeUnionView'; +export * from './UnionTypeView'; -export type NamedTypeView = (InterfaceTypeView | EnumTypeView | TypeUnionView) & { custom?: boolean; enum?: boolean; typeUnion?: boolean; }; +export type NamedTypeView = (InterfaceTypeView | EnumTypeView | UnionTypeView) & { custom?: boolean; enum?: boolean; unionType?: boolean; }; diff --git a/src/serializers.ts b/src/serializers.ts index 68d222b..e43679b 100644 --- a/src/serializers.ts +++ b/src/serializers.ts @@ -13,7 +13,7 @@ import { Module, ValueType, Value, - isTypeUnion, + isUnionType, } from './types'; const keywordColor = chalk.green; @@ -86,7 +86,7 @@ ${namedType.members .join('\n')} }`; } - if (isTypeUnion(namedType)) { + if (isUnionType(namedType)) { return `${documentationColor(customTags)} ${namedType.members .map( @@ -138,7 +138,7 @@ function serializeValueType(valueType: ValueType): string { return valueType.name; } - if (isTypeUnion(valueType)) { + if (isUnionType(valueType)) { return valueType.name; } diff --git a/src/types.ts b/src/types.ts index e9ec736..71e96e8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -32,8 +32,8 @@ export type NonEmptyType = | ArrayType | DictionaryType | PredefinedType - | UnionType - | TypeUnion; + | LiteralType + | UnionType; export enum ValueTypeKind { basicType = 'basicType', @@ -44,8 +44,8 @@ export enum ValueTypeKind { dictionaryType = 'dictionaryType', optionalType = 'optionalType', predefinedType = 'predefinedType', + literalType = 'literalType', unionType = 'unionType', - typeUnion = 'typeUnion', } interface BaseValueType { @@ -119,15 +119,15 @@ export interface PredefinedType extends BaseValueType { export type UnionLiteralType = string | number; -export interface UnionType extends BaseValueType { - kind: ValueTypeKind.unionType; +export interface LiteralType extends BaseValueType { + kind: ValueTypeKind.literalType; memberType: BasicTypeValue.string | BasicTypeValue.number; members: UnionLiteralType[]; } -export interface TypeUnion extends BaseValueType { +export interface UnionType extends BaseValueType { name: string; - kind: ValueTypeKind.typeUnion; + kind: ValueTypeKind.unionType; members: ValueType[]; customTags: Record; } @@ -164,12 +164,12 @@ export function isPredefinedType(valueType: ValueType): valueType is PredefinedT return valueType.kind === ValueTypeKind.predefinedType; } -export function isUnionType(valueType: ValueType): valueType is UnionType { - return valueType.kind === ValueTypeKind.unionType; +export function isLiteralType(valueType: ValueType): valueType is LiteralType { + return valueType.kind === ValueTypeKind.literalType; } -export function isTypeUnion(valueType: ValueType): valueType is TypeUnion { - return valueType.kind === ValueTypeKind.typeUnion; +export function isUnionType(valueType: ValueType): valueType is UnionType { + return valueType.kind === ValueTypeKind.unionType; } // TODO: Define these types to support recursive definition diff --git a/src/utils.ts b/src/utils.ts index bda2a3d..9917522 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,5 @@ import path from 'path'; -import { BasicTypeValue, EnumField, UnionType } from './types'; +import { BasicTypeValue, EnumField, LiteralType } from './types'; export function capitalize(text: string): string { if (text.length === 0) { @@ -37,11 +37,11 @@ export function uniquePathWithMethodReturnType(ownerName: string, methodName: st return `${capitalize(ownerName)}${capitalize(methodName)}ReturnType`; } -export function basicTypeOfUnion(union: UnionType): BasicTypeValue { +export function basicTypeOfUnion(union: LiteralType): BasicTypeValue { return union.memberType; } -export function membersOfUnion(union: UnionType): EnumField[] { +export function membersOfUnion(union: LiteralType): EnumField[] { const result: EnumField[] = []; union.members.forEach((value) => { let key = `${value}`; diff --git a/test/value-parser-test.ts b/test/value-parser-test.ts index a4d1036..e259d9e 100644 --- a/test/value-parser-test.ts +++ b/test/value-parser-test.ts @@ -296,7 +296,7 @@ describe('ValueParser', () => { testValueType('Multiple types union', 'string | number', { customTags: {}, name: '', - kind: ValueTypeKind.typeUnion, + kind: ValueTypeKind.unionType, members: [{ kind: ValueTypeKind.basicType, value: BasicTypeValue.string, From f197e2b1ce80a1e06d2aa6062157f8eede1e8425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Fri, 10 May 2024 16:28:09 +0800 Subject: [PATCH 6/8] Sorting members to minimize the impact of basic type --- demo/basic/generated/kotlin/BridgeTypes.kt | 58 ++++++++++---------- demo/basic/generated/swift/SharedTypes.swift | 30 +++++----- example-templates/kotlin-named-type.mustache | 45 +++------------ src/renderer/views/UnionTypeView.ts | 48 +++++++++------- 4 files changed, 79 insertions(+), 102 deletions(-) diff --git a/demo/basic/generated/kotlin/BridgeTypes.kt b/demo/basic/generated/kotlin/BridgeTypes.kt index 6448607..0e0aef5 100644 --- a/demo/basic/generated/kotlin/BridgeTypes.kt +++ b/demo/basic/generated/kotlin/BridgeTypes.kt @@ -120,11 +120,11 @@ data class OverriddenFullSizeMembersFooType( ) sealed class OverriddenFullSizeMembersUnionTypeType(val value: Any) { - data class StringValue(val value: String) : OverriddenFullSizeMembersUnionTypeType() - data class FloatValue(val value: Float) : OverriddenFullSizeMembersUnionTypeType() - data class BooleanValue(val value: Boolean) : OverriddenFullSizeMembersUnionTypeType() data class NumEnumValue(val value: NumEnum) : OverriddenFullSizeMembersUnionTypeType() data class DefaultEnumValue(val value: DefaultEnum) : OverriddenFullSizeMembersUnionTypeType() + data class FloatValue(val value: Float) : OverriddenFullSizeMembersUnionTypeType() + data class BooleanValue(val value: Boolean) : OverriddenFullSizeMembersUnionTypeType() + data class StringValue(val value: String) : OverriddenFullSizeMembersUnionTypeType() } class OverriddenFullSizeMembersUnionTypeTypeAdapter : JsonSerializer, JsonDeserializer { @@ -133,32 +133,32 @@ class OverriddenFullSizeMembersUnionTypeTypeAdapter : JsonSerializer { - val primitive = json.asJsonPrimitive - if (primitive.isString) { - return OverriddenFullSizeMembersUnionTypeType.StringValue(primitive.asString) - } - if (primitive.isNumber) { - return OverriddenFullSizeMembersUnionTypeType.FloatValue(primitive.asFloat) - } - if (primitive.isBoolean) { - return OverriddenFullSizeMembersUnionTypeType.BooleanValue(primitive.asBoolean) - } - } - json.isJsonObject -> { - try { - return OverriddenFullSizeMembersUnionTypeType.NumEnumValue(context.deserialize(json, NumEnum::class.java)) - } catch (e: Exception) { - // Ignore the exception and try the next type - } - try { - return OverriddenFullSizeMembersUnionTypeType.DefaultEnumValue(context.deserialize(json, DefaultEnum::class.java)) - } catch (e: Exception) { - // Ignore the exception and try the next type - } - } - else -> throw IllegalArgumentException("Unexpected JSON type: ${json.javaClass}") + try { + return OverriddenFullSizeMembersUnionTypeType.NumEnumValue(context.deserialize(json, NumEnum::class.java)) + } catch (e: Exception) { + // Ignore the exception and try the next type } + try { + return OverriddenFullSizeMembersUnionTypeType.DefaultEnumValue(context.deserialize(json, DefaultEnum::class.java)) + } catch (e: Exception) { + // Ignore the exception and try the next type + } + try { + return OverriddenFullSizeMembersUnionTypeType.FloatValue(context.deserialize(json, Float::class.java)) + } catch (e: Exception) { + // Ignore the exception and try the next type + } + try { + return OverriddenFullSizeMembersUnionTypeType.BooleanValue(context.deserialize(json, Boolean::class.java)) + } catch (e: Exception) { + // Ignore the exception and try the next type + } + try { + return OverriddenFullSizeMembersUnionTypeType.StringValue(context.deserialize(json, String::class.java)) + } catch (e: Exception) { + // Ignore the exception and try the next type + } + + throw IllegalArgumentException("Unexpected JSON type: ${json.javaClass}") } } diff --git a/demo/basic/generated/swift/SharedTypes.swift b/demo/basic/generated/swift/SharedTypes.swift index cca6d5d..e89854f 100644 --- a/demo/basic/generated/swift/SharedTypes.swift +++ b/demo/basic/generated/swift/SharedTypes.swift @@ -91,44 +91,44 @@ public struct OverriddenFullSizeMembersFooType: Codable { } public enum OverriddenFullSizeMembersUnionTypeType: Codable { - case string(_ value: String) - case double(_ value: Double) - case bool(_ value: Bool) case numEnum(_ value: NumEnum) case defaultEnum(_ value: DefaultEnum) + case bool(_ value: Bool) + case double(_ value: Double) + case string(_ value: String) public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() - if let value = try? container.decode(String.self) { - self = .string(value) + if let value = try? container.decode(NumEnum.self) { + self = .numEnum(value) } - else if let value = try? container.decode(Double.self) { - self = .double(value) + else if let value = try? container.decode(DefaultEnum.self) { + self = .defaultEnum(value) } else if let value = try? container.decode(Bool.self) { self = .bool(value) } - else if let value = try? container.decode(NumEnum.self) { - self = .numEnum(value) + else if let value = try? container.decode(Double.self) { + self = .double(value) } else { - let value = try container.decode(DefaultEnum.self) - self = .defaultEnum(value) + let value = try container.decode(String.self) + self = .string(value) } } public func encode(to encoder: any Encoder) throws { var container = encoder.singleValueContainer() switch self { - case .string(let value): + case .numEnum(let value): try container.encode(value) - case .double(let value): + case .defaultEnum(let value): try container.encode(value) case .bool(let value): try container.encode(value) - case .numEnum(let value): + case .double(let value): try container.encode(value) - case .defaultEnum(let value): + case .string(let value): try container.encode(value) } } diff --git a/example-templates/kotlin-named-type.mustache b/example-templates/kotlin-named-type.mustache index d7f2b0f..d96524d 100644 --- a/example-templates/kotlin-named-type.mustache +++ b/example-templates/kotlin-named-type.mustache @@ -51,44 +51,15 @@ class {{typeName}}Adapter : JsonSerializer<{{typeName}}>, JsonDeserializer<{{typ } override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): {{typeName}} { - when { - {{#hasBasicType}} - json.isJsonPrimitive -> { - val primitive = json.asJsonPrimitive - {{#members}} - {{#isNumber}} - if (primitive.isNumber) { - return {{typeName}}.{{type}}Value(primitive.asFloat) - } - {{/isNumber}} - {{#isBoolean}} - if (primitive.isBoolean) { - return {{typeName}}.{{type}}Value(primitive.asBoolean) - } - {{/isBoolean}} - {{#isString}} - if (primitive.isString) { - return {{typeName}}.{{type}}Value(primitive.asString) - } - {{/isString}} - {{/members}} - } - {{/hasBasicType}} - {{#hasTupleType}} - json.isJsonObject -> { - {{#members}} - {{#isTuple}} - try { - return {{typeName}}.{{type}}Value(context.deserialize(json, {{type}}::class.java)) - } catch (e: Exception) { - // Ignore the exception and try the next type - } - {{/isTuple}} - {{/members}} - } - {{/hasTupleType}} - else -> throw IllegalArgumentException("Unexpected JSON type: ${json.javaClass}") + {{#members}} + try { + return {{typeName}}.{{type}}Value(context.deserialize(json, {{type}}::class.java)) + } catch (e: Exception) { + // Ignore the exception and try the next type } + {{/members}} + + throw IllegalArgumentException("Unexpected JSON type: ${json.javaClass}") } } {{/unionType}} \ No newline at end of file diff --git a/src/renderer/views/UnionTypeView.ts b/src/renderer/views/UnionTypeView.ts index 0ea0c03..e76e613 100644 --- a/src/renderer/views/UnionTypeView.ts +++ b/src/renderer/views/UnionTypeView.ts @@ -12,41 +12,47 @@ export class UnionTypeView { return this.valueTransformer.convertTypeNameFromCustomMap(this.value.name); } - get hasBasicType(): boolean { - const { members } = this.value; - - return members.filter(isBasicType).length > 0; - } - - get hasTupleType(): boolean { - const { members } = this.value; - - return members.filter(isBasicType).length !== members.length; - } - get members(): { name: string, type: string; first: boolean; last: boolean; - isTuple: boolean; - isNumber: boolean; - isBoolean: boolean; - isString: boolean; }[] { const { members } = this.value; - return members.map((member, index) => { + return members + // put basic types to last + .sort((a, b) => { + if (isBasicType(a) && isBasicType(b)) { + // put string to last + if (a.value === 'string' && b.value === 'string') { + return 0; + } + + if (a.value === 'string') { + return 1; + } + + return -1; + } + + if (isBasicType(a) && !isBasicType(b)) { + return 1; + } + + if (!isBasicType(a) && isBasicType(b)) { + return -1; + } + + return 0; + }) + .map((member, index) => { const typeName = this.valueTransformer.convertValueType(member); return { name: uncapitalize(typeName), type: typeName, first: index === 0, last: index === members.length - 1, - isTuple: !isBasicType(member), - isNumber: isBasicType(member) && member.value === 'number', - isBoolean: isBasicType(member) && member.value === 'boolean', - isString: isBasicType(member) && member.value === 'string', }; }); } From db7a0cf0cc696e8bf5d845046ff96b997134b75d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Fri, 10 May 2024 17:53:24 +0800 Subject: [PATCH 7/8] Add support for Array and Dictionary --- demo/basic/generated/kotlin/BridgeTypes.kt | 12 +++++ demo/basic/generated/swift/SharedTypes.swift | 12 +++++ demo/basic/interfaces.ts | 2 +- example-templates/kotlin-named-type.mustache | 12 ++--- example-templates/swift-named-type.mustache | 10 ++-- src/renderer/views/UnionTypeView.ts | 51 +++++++++++++++----- 6 files changed, 75 insertions(+), 24 deletions(-) diff --git a/demo/basic/generated/kotlin/BridgeTypes.kt b/demo/basic/generated/kotlin/BridgeTypes.kt index 0e0aef5..a41328a 100644 --- a/demo/basic/generated/kotlin/BridgeTypes.kt +++ b/demo/basic/generated/kotlin/BridgeTypes.kt @@ -122,6 +122,8 @@ data class OverriddenFullSizeMembersFooType( sealed class OverriddenFullSizeMembersUnionTypeType(val value: Any) { data class NumEnumValue(val value: NumEnum) : OverriddenFullSizeMembersUnionTypeType() data class DefaultEnumValue(val value: DefaultEnum) : OverriddenFullSizeMembersUnionTypeType() + data class StringArrayValue(val value: Array) : OverriddenFullSizeMembersUnionTypeType() + data class StringForStringDictionaryValue(val value: Map) : OverriddenFullSizeMembersUnionTypeType() data class FloatValue(val value: Float) : OverriddenFullSizeMembersUnionTypeType() data class BooleanValue(val value: Boolean) : OverriddenFullSizeMembersUnionTypeType() data class StringValue(val value: String) : OverriddenFullSizeMembersUnionTypeType() @@ -143,6 +145,16 @@ class OverriddenFullSizeMembersUnionTypeTypeAdapter : JsonSerializer::class.java)) + } catch (e: Exception) { + // Ignore the exception and try the next type + } + try { + return OverriddenFullSizeMembersUnionTypeType.StringForStringDictionaryValue(context.deserialize(json, Map::class.java)) + } catch (e: Exception) { + // Ignore the exception and try the next type + } try { return OverriddenFullSizeMembersUnionTypeType.FloatValue(context.deserialize(json, Float::class.java)) } catch (e: Exception) { diff --git a/demo/basic/generated/swift/SharedTypes.swift b/demo/basic/generated/swift/SharedTypes.swift index e89854f..ebdbc22 100644 --- a/demo/basic/generated/swift/SharedTypes.swift +++ b/demo/basic/generated/swift/SharedTypes.swift @@ -93,6 +93,8 @@ public struct OverriddenFullSizeMembersFooType: Codable { public enum OverriddenFullSizeMembersUnionTypeType: Codable { case numEnum(_ value: NumEnum) case defaultEnum(_ value: DefaultEnum) + case stringArray(_ value: [String]) + case stringForStringDictionary(_ value: [String: String]) case bool(_ value: Bool) case double(_ value: Double) case string(_ value: String) @@ -105,6 +107,12 @@ public enum OverriddenFullSizeMembersUnionTypeType: Codable { else if let value = try? container.decode(DefaultEnum.self) { self = .defaultEnum(value) } + else if let value = try? container.decode([String].self) { + self = .stringArray(value) + } + else if let value = try? container.decode([String: String].self) { + self = .stringForStringDictionary(value) + } else if let value = try? container.decode(Bool.self) { self = .bool(value) } @@ -124,6 +132,10 @@ public enum OverriddenFullSizeMembersUnionTypeType: Codable { try container.encode(value) case .defaultEnum(let value): try container.encode(value) + case .stringArray(let value): + try container.encode(value) + case .stringForStringDictionary(let value): + try container.encode(value) case .bool(let value): try container.encode(value) case .double(let value): diff --git a/demo/basic/interfaces.ts b/demo/basic/interfaces.ts index c0c8f7e..630fc08 100644 --- a/demo/basic/interfaces.ts +++ b/demo/basic/interfaces.ts @@ -49,7 +49,7 @@ interface FullSize extends BaseSize, CustomSize { nullableStringUnion: 'A1' | 'B1' | null; numUnion1: 11 | 21; foo: { stringField: string } | { numberField: number }; - unionType: string | number | boolean | NumEnum | DefaultEnum; + unionType: string | number | boolean | NumEnum | DefaultEnum | string[] | DictionaryWithAnyKey; } interface DictionaryWithAnyKey { diff --git a/example-templates/kotlin-named-type.mustache b/example-templates/kotlin-named-type.mustache index d96524d..47cea2d 100644 --- a/example-templates/kotlin-named-type.mustache +++ b/example-templates/kotlin-named-type.mustache @@ -39,21 +39,21 @@ enum class {{typeName}} { {{/isStringType}} {{/enum}} {{#unionType}} -sealed class {{typeName}}(val value: Any) { +sealed class {{unionTypeName}}(val value: Any) { {{#members}} - data class {{type}}Value(val value: {{{type}}}) : {{typeName}}() + data class {{capitalizeName}}Value(val value: {{{type}}}) : {{unionTypeName}}() {{/members}} } -class {{typeName}}Adapter : JsonSerializer<{{typeName}}>, JsonDeserializer<{{typeName}}> { - override fun serialize(src: {{typeName}}, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { +class {{unionTypeName}}Adapter : JsonSerializer<{{unionTypeName}}>, JsonDeserializer<{{unionTypeName}}> { + override fun serialize(src: {{unionTypeName}}, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { return context.serialize(src.value) } - override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): {{typeName}} { + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): {{unionTypeName}} { {{#members}} try { - return {{typeName}}.{{type}}Value(context.deserialize(json, {{type}}::class.java)) + return {{unionTypeName}}.{{capitalizeName}}Value(context.deserialize(json, {{type}}::class.java)) } catch (e: Exception) { // Ignore the exception and try the next type } diff --git a/example-templates/swift-named-type.mustache b/example-templates/swift-named-type.mustache index ae684a8..ab08641 100644 --- a/example-templates/swift-named-type.mustache +++ b/example-templates/swift-named-type.mustache @@ -37,9 +37,9 @@ public enum {{typeName}}: {{valueType}}, Codable { } {{/enum}} {{#unionType}} -public enum {{typeName}}: Codable { +public enum {{unionTypeName}}: Codable { {{#members}} - case {{name}}(_ value: {{type}}) + case {{uncapitalizeName}}(_ value: {{type}}) {{/members}} public init(from decoder: any Decoder) throws { @@ -47,13 +47,13 @@ public enum {{typeName}}: Codable { {{#members}} {{^last}} {{^first}}else {{/first}}if let value = try? container.decode({{type}}.self) { - self = .{{name}}(value) + self = .{{uncapitalizeName}}(value) } {{/last}} {{#last}} else { let value = try container.decode({{type}}.self) - self = .{{name}}(value) + self = .{{uncapitalizeName}}(value) } {{/last}} {{/members}} @@ -63,7 +63,7 @@ public enum {{typeName}}: Codable { var container = encoder.singleValueContainer() switch self { {{#members}} - case .{{name}}(let value): + case .{{uncapitalizeName}}(let value): try container.encode(value) {{/members}} } diff --git a/src/renderer/views/UnionTypeView.ts b/src/renderer/views/UnionTypeView.ts index e76e613..5dd47be 100644 --- a/src/renderer/views/UnionTypeView.ts +++ b/src/renderer/views/UnionTypeView.ts @@ -1,5 +1,5 @@ -import { uncapitalize } from "../../utils"; -import { UnionType, isBasicType } from '../../types'; +import { capitalize, uncapitalize } from "../../utils"; +import { DictionaryKeyType, UnionType, ValueType, isArraryType, isBasicType, isDictionaryType } from '../../types'; import { ValueTransformer } from '../value-transformer'; export class UnionTypeView { @@ -8,12 +8,37 @@ export class UnionTypeView { private readonly valueTransformer: ValueTransformer ) { } - get typeName(): string { + get unionTypeName(): string { return this.valueTransformer.convertTypeNameFromCustomMap(this.value.name); } + convertValueTypeToUnionMemberName(valueType: ValueType): string { + if (isArraryType(valueType)) { + return `${this.valueTransformer.convertValueType(valueType.elementType)}Array`; + } + + if (isDictionaryType(valueType)) { + let keyType: string; + switch (valueType.keyType) { + case DictionaryKeyType.string: + keyType = 'String'; + break; + case DictionaryKeyType.number: + keyType = 'Int'; + break; + default: + throw Error('Type not exists'); + } + + return `${this.valueTransformer.convertValueType(valueType.valueType)}For${keyType}Dictionary`; + } + + return this.valueTransformer.convertValueType(valueType); + } + get members(): { - name: string, + capitalizeName: string, + uncapitalizeName: string, type: string; first: boolean; last: boolean; @@ -47,13 +72,15 @@ export class UnionTypeView { return 0; }) .map((member, index) => { - const typeName = this.valueTransformer.convertValueType(member); - return { - name: uncapitalize(typeName), - type: typeName, - first: index === 0, - last: index === members.length - 1, - }; - }); + const typeName = this.valueTransformer.convertValueType(member); + const memberName = this.convertValueTypeToUnionMemberName(member); + return { + capitalizeName: capitalize(memberName), + uncapitalizeName: uncapitalize(memberName), + type: typeName, + first: index === 0, + last: index === members.length - 1, + }; + }); } } From 9f5cb54f9e85a2ee6b36219b8ec914496518b787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Fri, 10 May 2024 18:30:12 +0800 Subject: [PATCH 8/8] Making Sorting Easier to Understand and sort Dictionary first --- demo/basic/generated/kotlin/BridgeTypes.kt | 6 +- src/renderer/views/UnionTypeView.ts | 47 +++++---- test/value-parser-test.ts | 114 ++++++++++++++++----- 3 files changed, 119 insertions(+), 48 deletions(-) diff --git a/demo/basic/generated/kotlin/BridgeTypes.kt b/demo/basic/generated/kotlin/BridgeTypes.kt index a41328a..3d4fdbc 100644 --- a/demo/basic/generated/kotlin/BridgeTypes.kt +++ b/demo/basic/generated/kotlin/BridgeTypes.kt @@ -124,8 +124,8 @@ sealed class OverriddenFullSizeMembersUnionTypeType(val value: Any) { data class DefaultEnumValue(val value: DefaultEnum) : OverriddenFullSizeMembersUnionTypeType() data class StringArrayValue(val value: Array) : OverriddenFullSizeMembersUnionTypeType() data class StringForStringDictionaryValue(val value: Map) : OverriddenFullSizeMembersUnionTypeType() - data class FloatValue(val value: Float) : OverriddenFullSizeMembersUnionTypeType() data class BooleanValue(val value: Boolean) : OverriddenFullSizeMembersUnionTypeType() + data class FloatValue(val value: Float) : OverriddenFullSizeMembersUnionTypeType() data class StringValue(val value: String) : OverriddenFullSizeMembersUnionTypeType() } @@ -156,12 +156,12 @@ class OverriddenFullSizeMembersUnionTypeTypeAdapter : JsonSerializer { - if (isBasicType(a) && isBasicType(b)) { - // put string to last - if (a.value === 'string' && b.value === 'string') { - return 0; - } + const dictionaryTypeMembers: DictionaryType[] = []; + let basicTypeMembers: BasicType[] = []; + const otherMembers: ValueType[] = []; - if (a.value === 'string') { - return 1; - } + members.forEach((member) => { + if (isDictionaryType(member)) { + dictionaryTypeMembers.push(member); + } else if (isBasicType(member)) { + basicTypeMembers.push(member); + } else { + otherMembers.push(member); + } + }); - return -1; - } + basicTypeMembers = basicTypeMembers.sort((a, b) => { + // put string to last + if (a.value === 'string' && b.value === 'string') { + return 0; + } - if (isBasicType(a) && !isBasicType(b)) { - return 1; - } + if (a.value === 'string') { + return 1; + } - if (!isBasicType(a) && isBasicType(b)) { - return -1; - } + return -1; + }); - return 0; - }) + const sortedMembers: ValueType[] = [...otherMembers, ...dictionaryTypeMembers, ...basicTypeMembers]; + return sortedMembers .map((member, index) => { const typeName = this.valueTransformer.convertValueType(member); const memberName = this.convertValueTypeToUnionMemberName(member); diff --git a/test/value-parser-test.ts b/test/value-parser-test.ts index e259d9e..9ba1ce5 100644 --- a/test/value-parser-test.ts +++ b/test/value-parser-test.ts @@ -2,7 +2,9 @@ import { describe, it } from 'mocha'; import { expect } from 'chai'; import { withTempMethodParser, withTempValueParser } from './utils'; import { ValueParserError } from '../src/parser/ValueParserError'; -import { BasicType, BasicTypeValue, DictionaryKeyType, DictionaryType, EnumSubType, EnumType, InterfaceType, OptionalType, PredefinedType, TupleType, ValueType, ValueTypeKind } from '../src/types'; +import { BasicType, BasicTypeValue, DictionaryKeyType, DictionaryType, EnumSubType, EnumType, InterfaceType, OptionalType, PredefinedType, TupleType, ValueType, ValueTypeKind, UnionType } from '../src/types'; +import { UnionTypeView } from '../src/renderer/views'; +import { SwiftValueTransformer } from '../src/renderer/value-transformer'; const stringType: BasicType = { kind: ValueTypeKind.basicType, value: BasicTypeValue.string }; const numberType: BasicType = { kind: ValueTypeKind.basicType, value: BasicTypeValue.number }; @@ -292,33 +294,99 @@ describe('ValueParser', () => { expect(parseFunc).to.throw('type null | undefined is invalid'); }); }); + const customCode = ` + interface Person { + name: string; + } + `; - testValueType('Multiple types union', 'string | number', { + let unionType: UnionType = { customTags: {}, name: '', kind: ValueTypeKind.unionType, - members: [{ - kind: ValueTypeKind.basicType, - value: BasicTypeValue.string, - }, { - kind: ValueTypeKind.basicType, - value: BasicTypeValue.number, - }], - }); - - const optionalStringType: OptionalType = { kind: ValueTypeKind.optionalType, wrappedType: stringType }; - - testValueType('null union', 'string | null', optionalStringType); - testValueType('undefined union', 'string | undefined', optionalStringType); - testValueType('null and undefined union', 'string | null | undefined', optionalStringType); - - const tupleType: TupleType = { - kind: ValueTypeKind.tupleType, - members: [{ name: 'stringField', type: stringType, documentation: '' }, { name: 'numberField', type: numberType, documentation: '' }], + members: [ + { + kind: ValueTypeKind.basicType, + value: BasicTypeValue.string, + }, + { + kind: ValueTypeKind.basicType, + value: BasicTypeValue.number, + }, + { + keyType: DictionaryKeyType.string, + kind: ValueTypeKind.dictionaryType, + valueType: { + kind: ValueTypeKind.basicType, + value: BasicTypeValue.string, + } + }, + { + elementType: { + kind: ValueTypeKind.basicType, + value: BasicTypeValue.string, + }, + kind: ValueTypeKind.arrayType + }, + { + customTags: {}, + documentation: "", + kind: ValueTypeKind.interfaceType, + members: [ + { + documentation: "", + name: "name", + type: { + "kind": ValueTypeKind.basicType, + "value": BasicTypeValue.string, + } + } + ], + methods: [], + name: "Person", + }, + ], }; - const optionalTupleType: OptionalType = { kind: ValueTypeKind.optionalType, wrappedType: tupleType }; - - testValueType('merged optional tuple union', '{ stringField: string } | { numberField: number } | null', optionalTupleType); + testValueType('Multiple types union', 'string | number | Record | string[] | Person', unionType, new Set(), customCode); + + const sortedMembers = new UnionTypeView(unionType, new SwiftValueTransformer({})).members; + expect(sortedMembers).to.deep.equal([ + { + capitalizeName: 'StringArray', + uncapitalizeName: 'stringArray', + type: '[String]', + first: true, + last: false + }, + { + capitalizeName: 'Person', + uncapitalizeName: 'person', + type: 'Person', + first: false, + last: false + }, + { + capitalizeName: 'StringForStringDictionary', + uncapitalizeName: 'stringForStringDictionary', + type: '[String: String]', + first: false, + last: false + }, + { + capitalizeName: 'Double', + uncapitalizeName: 'double', + type: 'Double', + first: false, + last: false + }, + { + capitalizeName: 'String', + uncapitalizeName: 'string', + type: 'String', + first: false, + last: true + }, + ]); }); describe('Parse alias type', () => {