From 84b9d475ff11940b48f6327c11d2105850b363e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Wed, 20 Dec 2023 15:01:06 +0800 Subject: [PATCH 01/15] Add support for union to enum --- demo/basic/generated/kotlin/IHtmlApi.kt | 67 +++++++++++++++ demo/basic/generated/swift/IHtmlApi.swift | 45 +++++++++- demo/basic/interfaces.ts | 8 +- src/generator/CodeGenerator.ts | 2 +- src/generator/named-types.ts | 83 +++++++++++++++---- src/parser/ValueParser.ts | 36 +++++++- .../KotlinValueTransformer.ts | 31 ++++--- .../SwiftValueTransformer.ts | 19 +++-- .../value-transformer/ValueTransformer.ts | 4 +- src/renderer/views/InterfaceTypeView.ts | 10 ++- src/renderer/views/MethodView.ts | 22 ++++- src/renderer/views/ModuleView.ts | 5 +- src/serializers.ts | 50 +++++++---- src/types.ts | 26 +++++- src/utils.ts | 58 +++++++++++++ 15 files changed, 396 insertions(+), 70 deletions(-) diff --git a/demo/basic/generated/kotlin/IHtmlApi.kt b/demo/basic/generated/kotlin/IHtmlApi.kt index 72efcf1..1de0812 100644 --- a/demo/basic/generated/kotlin/IHtmlApi.kt +++ b/demo/basic/generated/kotlin/IHtmlApi.kt @@ -30,6 +30,8 @@ interface IHtmlApiBridge { fun requestRenderingResult() fun getSize(callback: Callback) fun getAliasSize(callback: Callback) + fun getName(callback: Callback) + fun getAge(sex: IHtmlApiGetAgeSex, callback: Callback) fun testDictionaryWithAnyKey(dict: Map) } @@ -69,6 +71,16 @@ open class IHtmlApiBridge(editor: WebEditor, gson: Gson) : JsBridge(editor, gson executeJsForResponse(JSBaseSize::class.java, "getAliasSize", callback) } + override fun getName(callback: Callback) { + executeJsForResponse(IHtmlApiGetNameReturnType::class.java, "getName", callback) + } + + override fun getAge(sex: IHtmlApiGetAgeSex, callback: Callback) { + executeJsForResponse(IHtmlApiGetAgeReturnType::class.java, "getAge", callback, mapOf( + "sex" to sex + )) + } + override fun testDictionaryWithAnyKey(dict: Map) { executeJs("testDictionaryWithAnyKey", mapOf( "dict" to dict @@ -82,6 +94,8 @@ data class OverriddenFullSize( @JvmField val stringEnum: StringEnum, @JvmField val numEnum: NumEnum, @JvmField val defEnum: DefaultEnum, + @JvmField val stringUnion1: OverriddenFullSizeStringUnion1, + @JvmField val numUnion1: OverriddenFullSizeNumUnion1, @JvmField val width: Float, @JvmField val height: Float, @JvmField val scale: Float, @@ -131,7 +145,60 @@ class DefaultEnumTypeAdapter : JsonSerializer, JsonDeserializer, JsonDeserializer { + override fun serialize(obj: OverriddenFullSizeNumUnion1, type: Type, context: JsonSerializationContext): JsonElement { + return JsonPrimitive(obj.value) + } + + override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): OverriddenFullSizeNumUnion1? { + return OverriddenFullSizeNumUnion1.find(json.asInt) + } +} + data class JSBaseSize( @JvmField val width: Float, @JvmField val height: Float, ) + +enum class IHtmlApiGetNameReturnType { + @SerializedName("A2") A2, + @SerializedName("B2") B2 +} + +enum class IHtmlApiGetAgeSex { + @SerializedName("Male") MALE, + @SerializedName("Female") FEMALE +} + +enum class IHtmlApiGetAgeReturnType(val value: Int) { + _21(21), + _22(22); + + companion object { + fun find(value: Int) = values().find { it.value == value } + } +} + +class IHtmlApiGetAgeReturnTypeTypeAdapter : JsonSerializer, JsonDeserializer { + override fun serialize(obj: IHtmlApiGetAgeReturnType, type: Type, context: JsonSerializationContext): JsonElement { + return JsonPrimitive(obj.value) + } + + override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): IHtmlApiGetAgeReturnType? { + return IHtmlApiGetAgeReturnType.find(json.asInt) + } +} diff --git a/demo/basic/generated/swift/IHtmlApi.swift b/demo/basic/generated/swift/IHtmlApi.swift index 5a10aee..920d27f 100644 --- a/demo/basic/generated/swift/IHtmlApi.swift +++ b/demo/basic/generated/swift/IHtmlApi.swift @@ -62,6 +62,20 @@ public class IHtmlApi { jsExecutor.execute(with: "htmlApi", feature: "getAliasSize", args: nil, completion: completion) } + public func getName(completion: @escaping BridgeCompletion) { + jsExecutor.execute(with: "htmlApi", feature: "getName", args: nil, completion: completion) + } + + public func getAge(sex: IHtmlApiGetAgeSex, completion: @escaping BridgeCompletion) { + struct Args: Encodable { + let sex: IHtmlApiGetAgeSex + } + let args = Args( + sex: sex + ) + jsExecutor.execute(with: "htmlApi", feature: "getAge", args: args, completion: completion) + } + public func testDictionaryWithAnyKey(dict: [String: String], completion: BridgeJSExecutor.Completion? = nil) { struct Args: Encodable { let dict: [String: String] @@ -80,18 +94,22 @@ public struct OverriddenFullSize: Codable { public var stringEnum: StringEnum public var numEnum: NumEnum public var defEnum: DefaultEnum + public var stringUnion1: OverriddenFullSizeStringUnion1 + public var numUnion1: OverriddenFullSizeNumUnion1 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, width: Double, height: Double, scale: Double) { + public init(size: Double, count: Int, stringEnum: StringEnum, numEnum: NumEnum, defEnum: DefaultEnum, stringUnion1: OverriddenFullSizeStringUnion1, numUnion1: OverriddenFullSizeNumUnion1, width: Double, height: Double, scale: Double) { self.size = size self.count = count self.stringEnum = stringEnum self.numEnum = numEnum self.defEnum = defEnum + self.stringUnion1 = stringUnion1 + self.numUnion1 = numUnion1 self.width = width self.height = height self.scale = scale @@ -114,6 +132,16 @@ public enum DefaultEnum: Int, Codable { case defaultValueD = 1 } +public enum OverriddenFullSizeStringUnion1: String, Codable { + case a1 = "A1" + case b1 = "B1" +} + +public enum OverriddenFullSizeNumUnion1: Int, Codable { + case _11 = 11 + case _21 = 21 +} + public struct BaseSize: Codable { public var width: Double public var height: Double @@ -123,3 +151,18 @@ public struct BaseSize: Codable { self.height = height } } + +public enum IHtmlApiGetNameReturnType: String, Codable { + case a2 = "A2" + case b2 = "B2" +} + +public enum IHtmlApiGetAgeSex: String, Codable { + case male = "Male" + case female = "Female" +} + +public enum IHtmlApiGetAgeReturnType: Int, Codable { + case _21 = 21 + case _22 = 22 +} diff --git a/demo/basic/interfaces.ts b/demo/basic/interfaces.ts index b08689a..5ff5988 100644 --- a/demo/basic/interfaces.ts +++ b/demo/basic/interfaces.ts @@ -36,14 +36,16 @@ enum DefaultEnum { */ interface FullSize extends BaseSize, CustomSize { /** - * Example documentation for member - */ + * Example documentation for member + */ member: NumEnum.one; size: number; count: CodeGen_Int; stringEnum: StringEnum; numEnum: NumEnum; defEnum: DefaultEnum; + stringUnion1: 'A1' | 'B1'; + numUnion1: 11 | 21; } interface DictionaryWithAnyKey { @@ -68,6 +70,8 @@ export interface IHtmlApi { requestRenderingResult(): void; getSize(): FullSize; getAliasSize(): AliasSize; + getName(): 'A2' | 'B2'; + getAge({ sex }: { sex: 'Male' | 'Female' }): 21 | 22; testDictionaryWithAnyKey({ dict }: { dict: DictionaryWithAnyKey }): void; } diff --git a/src/generator/CodeGenerator.ts b/src/generator/CodeGenerator.ts index 0e8c937..47b5fa4 100644 --- a/src/generator/CodeGenerator.ts +++ b/src/generator/CodeGenerator.ts @@ -76,7 +76,7 @@ export class CodeGenerator { printSharedTypes(sharedTypes: NamedTypeInfo[]): void { console.log('Shared named types:\n'); - console.log(sharedTypes.map((namedType) => serializeNamedType(namedType.type)).join('\n\n')); + console.log(sharedTypes.map((namedType) => serializeNamedType(namedType.type, '')).join('\n\n')); } renderModules(modules: ParsedModule[], options: RenderOptions): void { diff --git a/src/generator/named-types.ts b/src/generator/named-types.ts index 61b8ced..348211c 100644 --- a/src/generator/named-types.ts +++ b/src/generator/named-types.ts @@ -1,4 +1,11 @@ -import { capitalize } from '../utils'; +import { + capitalize, + basicTypeOfUnion, + membersOfUnion, + uniqueNameAsMember, + uniqueNameAsMethodParameter, + uniqueNameAsMethodReturnType, +} from '../utils'; import { isArraryType, isInterfaceType, @@ -12,6 +19,8 @@ import { TupleType, isTupleType, ValueTypeKind, + isUnionType, + EnumSubType, } from '../types'; export const enum ValueTypeSource { @@ -40,8 +49,8 @@ export type NamedTypesResult = { associatedTypes: Record fetchRootTypes(module)) - .forEach(([valueType]) => { - recursiveVisitMembersType(valueType, (namedType) => { + .forEach(({ valueType, uniqueName }) => { + recursiveVisitMembersType(valueType, uniqueName, (namedType) => { if (!isInterfaceType(namedType)) { return; } @@ -97,8 +106,8 @@ function fetchNamedTypes(modules: Module[]): NamedTypesResult { const typeMap: Record }> = {}; modules.forEach((module) => { - fetchRootTypes(module).forEach(([valueType, source]) => { - recursiveVisitMembersType(valueType, (membersType, path) => { + fetchRootTypes(module).forEach(({ valueType, source, uniqueName }) => { + recursiveVisitMembersType(valueType, uniqueName, (membersType, path) => { let namedType = membersType; if (isTupleType(namedType)) { namedType = membersType as unknown as InterfaceType; @@ -138,15 +147,30 @@ function fetchNamedTypes(modules: Module[]): NamedTypesResult { return { associatedTypes, sharedTypes }; } -function fetchRootTypes(module: Module): [ValueType, ValueTypeSource][] { - const typesInMembers: [ValueType, ValueTypeSource][] = module.members.map((field) => [ - field.type, - ValueTypeSource.Field, - ]); - const typesInMethods: [ValueType, ValueTypeSource][] = module.methods.flatMap((method) => +function fetchRootTypes(module: Module): { valueType: ValueType; source: ValueTypeSource; uniqueName: string }[] { + const typesInMembers: ReturnType = module.members.map((field) => ({ + valueType: field.type, + source: ValueTypeSource.Field, + uniqueName: uniqueNameAsMember(module.name, field.name), + })); + const typesInMethods: ReturnType = module.methods.flatMap((method) => method.parameters - .map((parameter): [ValueType, ValueTypeSource] => [parameter.type, ValueTypeSource.Parameter]) - .concat(method.returnType ? [[method.returnType, ValueTypeSource.Return]] : []) + .map((parameter) => ({ + valueType: parameter.type, + source: ValueTypeSource.Parameter, + uniqueName: uniqueNameAsMethodParameter(module.name, method.name, parameter.name), + })) + .concat( + method.returnType + ? [ + { + valueType: method.returnType, + source: ValueTypeSource.Return, + uniqueName: uniqueNameAsMethodReturnType(module.name, method.name), + }, + ] + : [] + ) ); return typesInMembers.concat(typesInMethods); @@ -154,6 +178,7 @@ function fetchRootTypes(module: Module): [ValueType, ValueTypeSource][] { function recursiveVisitMembersType( valueType: ValueType, + uniqueName: string, visit: (membersType: NamedType | TupleType, path: string) => void, path = '' ): void { @@ -161,7 +186,12 @@ function recursiveVisitMembersType( visit(valueType, path); valueType.members.forEach((member) => { - recursiveVisitMembersType(member.type, visit, `${path}${valueType.name}Members${capitalize(member.name)}Type`); + recursiveVisitMembersType( + member.type, + `${capitalize(valueType.name)}${capitalize(member.name)}`, + visit, + `${path}${valueType.name}Members${capitalize(member.name)}Type` + ); }); return; @@ -171,7 +201,7 @@ function recursiveVisitMembersType( visit(valueType, path); valueType.members.forEach((member) => { - recursiveVisitMembersType(member.type, visit, `${path}Members${capitalize(member.name)}Type`); + recursiveVisitMembersType(member.type, uniqueName, visit, `${path}Members${capitalize(member.name)}Type`); }); return; @@ -183,16 +213,33 @@ function recursiveVisitMembersType( } if (isArraryType(valueType)) { - recursiveVisitMembersType(valueType.elementType, visit, `${path}Element`); + recursiveVisitMembersType(valueType.elementType, uniqueName, visit, `${path}Element`); return; } if (isDictionaryType(valueType)) { - recursiveVisitMembersType(valueType.valueType, visit, `${path}Value`); + recursiveVisitMembersType(valueType.valueType, uniqueName, visit, `${path}Value`); return; } if (isOptionalType(valueType)) { - recursiveVisitMembersType(valueType.wrappedType, visit, `${path}`); + recursiveVisitMembersType(valueType.wrappedType, uniqueName, visit, `${path}`); + return; + } + + if (isUnionType(valueType)) { + const subType = basicTypeOfUnion(valueType); + const subNamedType: EnumType = { + kind: ValueTypeKind.enumType, + name: uniqueName, + subType: subType === 'number' ? EnumSubType.number : EnumSubType.string, + members: membersOfUnion(valueType), + documentation: '', + customTags: {}, + }; + visit(subNamedType, path); + return; } + + console.log(`Unhandled value type ${JSON.stringify(valueType)}`); } diff --git a/src/parser/ValueParser.ts b/src/parser/ValueParser.ts index 682c325..6009936 100644 --- a/src/parser/ValueParser.ts +++ b/src/parser/ValueParser.ts @@ -16,11 +16,12 @@ import { DictionaryKeyType, isInterfaceType, PredefinedType, - Value, TupleType, isTupleType, EnumField, isBasicType, + LiteralType, + UnionType, } from '../types'; import { isUndefinedOrNull, parseTypeJSDocTags } from './utils'; import { ParserLogger } from '../logger/ParserLogger'; @@ -243,12 +244,18 @@ export class ValueParser { let nullable = false; let valueType: ValueType | undefined; + const literalValues: LiteralType[] = []; node.types.forEach((typeNode) => { if (isUndefinedOrNull(typeNode)) { nullable = true; return; } + const literalKind = this.parseLiteralNode(typeNode); + if (literalKind !== null) { + literalValues.push(literalKind); + return; + } const newValueType = this.valueTypeFromTypeNode(typeNode); @@ -276,6 +283,28 @@ export class ValueParser { }; }); + if (literalValues.length > 0) { + const kindSet = new Set( + literalValues.map((obj) => { + if ('value' in obj.type) { + return obj.type.value; + } + return null; + }) + ); + + if (kindSet.size !== 1) { + throw new ValueParserError( + `union type ${node.getText()} has multiple literal type`, + 'Union type must contain only one supported literal type' + ); + } + const unionKind: UnionType = { + kind: ValueTypeKind.unionType, + value: literalValues, + }; + return unionKind; + } if (!valueType) { throw new ValueParserError( `union type ${node.getText()} is invalid`, @@ -649,10 +678,11 @@ export class ValueParser { }; } - private parseLiteralNode(typeNode: ts.TypeNode): { type: ValueType; value: Value } | null { + private parseLiteralNode(typeNode: ts.TypeNode): LiteralType | null { if (ts.isLiteralTypeNode(typeNode)) { if (ts.isNumericLiteral(typeNode.literal)) { return { + kind: ValueTypeKind.literalType, type: { kind: ValueTypeKind.basicType, value: BasicTypeValue.number, @@ -663,6 +693,7 @@ export class ValueParser { if (ts.isStringLiteral(typeNode.literal)) { return { + kind: ValueTypeKind.literalType, type: { kind: ValueTypeKind.basicType, value: BasicTypeValue.string, @@ -692,6 +723,7 @@ export class ValueParser { throw Error(`Invalid enum member ${typeName}`); } return { + kind: ValueTypeKind.literalType, type: enumType, value: referencedNode.name.getText(), }; diff --git a/src/renderer/value-transformer/KotlinValueTransformer.ts b/src/renderer/value-transformer/KotlinValueTransformer.ts index f32f47f..a82da99 100644 --- a/src/renderer/value-transformer/KotlinValueTransformer.ts +++ b/src/renderer/value-transformer/KotlinValueTransformer.ts @@ -10,13 +10,14 @@ import { isPredefinedType, ValueType, Value, + isUnionType, } from '../../types'; import { ValueTransformer } from './ValueTransformer'; export class KotlinValueTransformer implements ValueTransformer { constructor(private readonly typeNameMap: Record) {} - convertValueType(valueType: ValueType): string { + convertValueType(valueType: ValueType, uniqueName: string): string { if (isBasicType(valueType)) { switch (valueType.value) { case BasicTypeValue.string: @@ -39,7 +40,7 @@ export class KotlinValueTransformer implements ValueTransformer { } if (isArraryType(valueType)) { - return `Array<${this.convertValueType(valueType.elementType)}>`; + return `Array<${this.convertValueType(valueType.elementType, uniqueName)}>`; } if (isDictionaryType(valueType)) { @@ -55,26 +56,30 @@ export class KotlinValueTransformer implements ValueTransformer { throw Error('Type not exists'); } - return `Map<${keyType}, ${this.convertValueType(valueType.valueType)}>`; + return `Map<${keyType}, ${this.convertValueType(valueType.valueType, uniqueName)}>`; } if (isOptionalType(valueType)) { - return `${this.convertValueType(valueType.wrappedType)}?`; + return `${this.convertValueType(valueType.wrappedType, uniqueName)}?`; } if (isPredefinedType(valueType)) { return this.typeNameMap[valueType.name] ?? valueType.name; } + if (isUnionType(valueType)) { + return uniqueName; + } + throw Error('Type not handled'); } - convertNonOptionalValueType(valueType: ValueType): string { + convertNonOptionalValueType(valueType: ValueType, uniqueName: string): string { if (isOptionalType(valueType)) { - return this.convertValueType(valueType.wrappedType); + return this.convertValueType(valueType.wrappedType, uniqueName); } - return this.convertValueType(valueType); + return this.convertValueType(valueType, uniqueName); } convertValue(value: Value, type: ValueType): string { @@ -120,10 +125,14 @@ export class KotlinValueTransformer implements ValueTransformer { } convertEnumKey(text: string): string { - return text - .replace(/\.?([A-Z]+)/g, (_, p1: string) => `_${p1}`) - .replace(/^_/, '') - .toUpperCase(); + let result = text.replace(/\.?([A-Z]+)/g, (_, p1: string) => `_${p1}`); + + const testText = result.replace(/^_/, ''); + if (Number.isNaN(Number(testText))) { + result = testText; + } + + return result.toUpperCase(); } convertTypeNameFromCustomMap(name: string): string { diff --git a/src/renderer/value-transformer/SwiftValueTransformer.ts b/src/renderer/value-transformer/SwiftValueTransformer.ts index 891af84..98010a4 100644 --- a/src/renderer/value-transformer/SwiftValueTransformer.ts +++ b/src/renderer/value-transformer/SwiftValueTransformer.ts @@ -10,13 +10,14 @@ import { isPredefinedType, ValueType, Value, + isUnionType, } from '../../types'; import { ValueTransformer } from './ValueTransformer'; export class SwiftValueTransformer implements ValueTransformer { constructor(private readonly typeNameMap: Record) {} - convertValueType(valueType: ValueType): string { + convertValueType(valueType: ValueType, uniqueName: string): string { if (isBasicType(valueType)) { switch (valueType.value) { case BasicTypeValue.string: @@ -39,7 +40,7 @@ export class SwiftValueTransformer implements ValueTransformer { } if (isArraryType(valueType)) { - return `[${this.convertValueType(valueType.elementType)}]`; + return `[${this.convertValueType(valueType.elementType, uniqueName)}]`; } if (isDictionaryType(valueType)) { @@ -55,26 +56,30 @@ export class SwiftValueTransformer implements ValueTransformer { throw Error('Type not exists'); } - return `[${keyType}: ${this.convertValueType(valueType.valueType)}]`; + return `[${keyType}: ${this.convertValueType(valueType.valueType, uniqueName)}]`; } if (isOptionalType(valueType)) { - return `${this.convertValueType(valueType.wrappedType)}?`; + return `${this.convertValueType(valueType.wrappedType, uniqueName)}?`; } if (isPredefinedType(valueType)) { return this.typeNameMap[valueType.name] ?? valueType.name; } + if (isUnionType(valueType)) { + return uniqueName; + } + throw Error('Type not handled'); } - convertNonOptionalValueType(valueType: ValueType): string { + convertNonOptionalValueType(valueType: ValueType, uniqueName: string): string { if (isOptionalType(valueType)) { - return this.convertValueType(valueType.wrappedType); + return this.convertValueType(valueType.wrappedType, uniqueName); } - return this.convertValueType(valueType); + return this.convertValueType(valueType, uniqueName); } convertValue(value: Value, type: ValueType): string { diff --git a/src/renderer/value-transformer/ValueTransformer.ts b/src/renderer/value-transformer/ValueTransformer.ts index 6ced0b6..e9d7761 100644 --- a/src/renderer/value-transformer/ValueTransformer.ts +++ b/src/renderer/value-transformer/ValueTransformer.ts @@ -1,8 +1,8 @@ import { ValueType, Value } from '../../types'; export interface ValueTransformer { - convertValueType(valueType: ValueType): string; - convertNonOptionalValueType(valueType: ValueType): string; + convertValueType(valueType: ValueType, uniqueName: string): string; + convertNonOptionalValueType(valueType: ValueType, uniqueName: string): string; convertValue(value: Value, type: ValueType): string; convertEnumKey(text: string): string; convertTypeNameFromCustomMap(name: string): string; diff --git a/src/renderer/views/InterfaceTypeView.ts b/src/renderer/views/InterfaceTypeView.ts index 02a55db..ac54622 100644 --- a/src/renderer/views/InterfaceTypeView.ts +++ b/src/renderer/views/InterfaceTypeView.ts @@ -1,5 +1,6 @@ import { ValueTypeSource } from '../../generator/named-types'; import { InterfaceType } from '../../types'; +import { uniqueNameAsMember } from '../../utils'; import { getDocumentationLines } from '../utils'; import { ValueTransformer } from '../value-transformer'; @@ -11,21 +12,22 @@ export class InterfaceTypeView { ) {} get typeName(): string { - return this.valueTransformer.convertValueType(this.interfaceType); + return this.valueTransformer.convertValueType(this.interfaceType, ''); } get members(): { name: string; type: string; documentationLines: string[]; last: boolean }[] { const members = this.interfaceType.members.filter((member) => member.staticValue === undefined); - + const { typeName } = this; return members.map((member, index) => ({ name: member.name, - type: this.valueTransformer.convertValueType(member.type), + type: this.valueTransformer.convertValueType(member.type, uniqueNameAsMember(typeName, member.name)), documentationLines: getDocumentationLines(member.documentation), last: index === members.length - 1, })); } get staticMembers(): { name: string; type: string; value: string; documentationLines: string[] }[] { + const { typeName } = this; return this.interfaceType.members .filter((member) => member.staticValue !== undefined) .map((member) => { @@ -35,7 +37,7 @@ export class InterfaceTypeView { return { name: member.name, - type: this.valueTransformer.convertValueType(member.type), + type: this.valueTransformer.convertValueType(member.type, uniqueNameAsMember(typeName, member.name)), value: this.valueTransformer.convertValue(member.staticValue, member.type), documentationLines: getDocumentationLines(member.documentation), }; diff --git a/src/renderer/views/MethodView.ts b/src/renderer/views/MethodView.ts index bea827e..5309a34 100644 --- a/src/renderer/views/MethodView.ts +++ b/src/renderer/views/MethodView.ts @@ -1,9 +1,14 @@ import { Method } from '../../types'; +import { uniqueNameAsMethodParameter, uniqueNameAsMethodReturnType } from '../../utils'; import { getDocumentationLines } from '../utils'; import { ValueTransformer } from '../value-transformer'; export class MethodView { - constructor(private readonly method: Method, private readonly valueTransformer: ValueTransformer) {} + constructor( + private readonly method: Method, + private readonly valueTransformer: ValueTransformer, + private readonly ownerName: string + ) {} get methodName(): string { return this.method.name; @@ -16,7 +21,10 @@ export class MethodView { get parameters(): { name: string; type: string; last: boolean }[] { return this.method.parameters.map((parameter, index) => ({ name: parameter.name, - type: this.valueTransformer.convertValueType(parameter.type), + type: this.valueTransformer.convertValueType( + parameter.type, + uniqueNameAsMethodParameter(this.ownerName, this.methodName, parameter.name) + ), last: index === this.method.parameters.length - 1, })); } @@ -26,7 +34,10 @@ export class MethodView { return null; } - return this.valueTransformer.convertValueType(this.method.returnType); + return this.valueTransformer.convertValueType( + this.method.returnType, + uniqueNameAsMethodReturnType(this.ownerName, this.methodName) + ); } get nonOptionalReturnType(): string | null { @@ -34,7 +45,10 @@ export class MethodView { return null; } - return this.valueTransformer.convertNonOptionalValueType(this.method.returnType); + return this.valueTransformer.convertNonOptionalValueType( + this.method.returnType, + uniqueNameAsMethodReturnType(this.ownerName, this.methodName) + ); } get documentationLines(): string[] { diff --git a/src/renderer/views/ModuleView.ts b/src/renderer/views/ModuleView.ts index 671d90e..0caf087 100644 --- a/src/renderer/views/ModuleView.ts +++ b/src/renderer/views/ModuleView.ts @@ -1,4 +1,5 @@ import { Module } from '../../types'; +import { uniqueNameAsMember } from '../../utils'; import { ValueTransformer } from '../value-transformer/ValueTransformer'; import { MethodView } from './MethodView'; import { NamedTypeView } from './index'; @@ -20,14 +21,14 @@ export class ModuleView { return members.map((member, index) => ({ name: member.name, - type: this.valueTransformer.convertValueType(member.type), + type: this.valueTransformer.convertValueType(member.type, uniqueNameAsMember(this.module.name, member.name)), documentationLines: getDocumentationLines(member.documentation), last: index === members.length - 1, })); } get methods(): MethodView[] { - return this.module.methods.map((method) => new MethodView(method, this.valueTransformer)); + return this.module.methods.map((method) => new MethodView(method, this.valueTransformer, this.module.name)); } get exportedInterfaceBases(): Record { diff --git a/src/serializers.ts b/src/serializers.ts index 7ee1dea..a836061 100644 --- a/src/serializers.ts +++ b/src/serializers.ts @@ -13,7 +13,9 @@ import { Module, ValueType, Value, + isUnionType, } from './types'; +import { uniqueNameAsMember, uniqueNameAsMethodParameter, uniqueNameAsMethodReturnType } from './utils'; const keywordColor = chalk.green; const identifierColor = chalk.blue; @@ -22,7 +24,7 @@ const valueColor = chalk.cyan; const documentationColor = chalk.gray; export function serializeModule(module: Module, associatedTypes: NamedType[]): string { - const serializedAssociatedTypes = associatedTypes.map((associatedType) => serializeNamedType(associatedType)); + const serializedAssociatedTypes = associatedTypes.map((associatedType) => serializeNamedType(associatedType, '')); const customTags = Object.keys(module.customTags).length > 0 ? `Custom tags: ${JSON.stringify(module.customTags)}\n` : ''; @@ -30,7 +32,10 @@ export function serializeModule(module: Module, associatedTypes: NamedType[]): s module.name } { ${module.members - .map((member) => `${serializeDocumentation(member.documentation)}${keywordColor('var')} ${serializeField(member)}`) + .map( + (member) => + `${serializeDocumentation(member.documentation)}${keywordColor('var')} ${serializeField(member, module.name)}` + ) .join('\n') .split('\n') .map((line) => ` ${line}`) @@ -38,7 +43,7 @@ ${module.members ${module.methods .map((method) => - serializeMethod(method) + serializeMethod(method, module.name) .split('\n') .map((line) => ` ${line}`) .join('\n') @@ -55,7 +60,7 @@ ${module.methods }`; } -export function serializeNamedType(namedType: NamedType): string { +export function serializeNamedType(namedType: NamedType, ownerName: string): string { const customTags = Object.keys(namedType.customTags).length > 0 ? `Custom tags: ${JSON.stringify(namedType.customTags)}\n` : ''; @@ -64,7 +69,10 @@ export function serializeNamedType(namedType: NamedType): string { 'Type' )} ${namedType.name} { ${namedType.members - .map((member) => `${serializeDocumentation(member.documentation)}${keywordColor('var')} ${serializeField(member)}`) + .map( + (member) => + `${serializeDocumentation(member.documentation)}${keywordColor('var')} ${serializeField(member, ownerName)}` + ) .join('\n') .split('\n') .map((line) => ` ${line}`) @@ -87,23 +95,32 @@ ${namedType.members }`; } -function serializeMethod(method: Method): string { +function serializeMethod(method: Method, ownerName: string): string { const serializedReturnType = - method.returnType !== null ? `: ${typeColor(serializeValueType(method.returnType))}` : ''; + method.returnType !== null + ? `: ${typeColor(serializeValueType(method.returnType, uniqueNameAsMethodReturnType(ownerName, method.name)))}` + : ''; return `${serializeDocumentation(method.documentation)}${keywordColor('func')} ${identifierColor( method.name )}(${method.parameters - .map((parameter) => `${parameter.name}: ${typeColor(serializeValueType(parameter.type))}`) + .map( + (parameter) => + `${parameter.name}: ${typeColor( + serializeValueType(parameter.type, uniqueNameAsMethodParameter(ownerName, method.name, parameter.name)) + )}` + ) .join(', ')})${serializedReturnType}`; } -function serializeField(field: Field): string { +function serializeField(field: Field, ownerName: string): string { const staticValue = field.staticValue !== undefined ? ` = ${serializeStaticValue(field.staticValue, field.type)}` : ''; - return `${identifierColor(field.name)}: ${typeColor(serializeValueType(field.type))}${staticValue}`; + return `${identifierColor(field.name)}: ${typeColor( + serializeValueType(field.type, uniqueNameAsMember(ownerName, field.name)) + )}${staticValue}`; } -function serializeValueType(valueType: ValueType): string { +function serializeValueType(valueType: ValueType, uniqueTypeName: string): string { if (isBasicType(valueType)) { return valueType.value; } @@ -114,17 +131,20 @@ function serializeValueType(valueType: ValueType): string { return valueType.name; } if (isArraryType(valueType)) { - return `[${serializeValueType(valueType.elementType)}]`; + return `[${serializeValueType(valueType.elementType, uniqueTypeName)}]`; } if (isDictionaryType(valueType)) { - return `[${valueType.keyType}: ${serializeValueType(valueType.valueType)}]`; + return `[${valueType.keyType}: ${serializeValueType(valueType.valueType, uniqueTypeName)}]`; } if (isOptionalType(valueType)) { - return `${serializeValueType(valueType.wrappedType)}?`; + return `${serializeValueType(valueType.wrappedType, uniqueTypeName)}?`; } if (isPredefinedType(valueType)) { return valueType.name; } + if (isUnionType(valueType)) { + return uniqueTypeName; + } throw Error(`Unhandled value type ${JSON.stringify(valueType)}`); } @@ -149,4 +169,4 @@ ${documentation .join('\n')} */ `); -} +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index 2db2fdc..95a5792 100644 --- a/src/types.ts +++ b/src/types.ts @@ -30,7 +30,9 @@ export type NonEmptyType = | EnumType | ArrayType | DictionaryType - | PredefinedType; + | PredefinedType + | LiteralType + | UnionType; export enum ValueTypeKind { basicType = 'basicType', @@ -41,6 +43,8 @@ export enum ValueTypeKind { dictionaryType = 'dictionaryType', optionalType = 'optionalType', predefinedType = 'predefinedType', + literalType = 'literalType', + unionType = 'unionType', } interface BaseValueType { @@ -113,6 +117,22 @@ export interface PredefinedType extends BaseValueType { name: string; } +export interface LiteralType extends BaseValueType { + kind: ValueTypeKind.literalType; + type: + | { + kind: ValueTypeKind.basicType; + value: BasicTypeValue.string | BasicTypeValue.number | BasicTypeValue.boolean; + } + | EnumType; + value: Value; +} + +export interface UnionType extends BaseValueType { + kind: ValueTypeKind.unionType; + value: LiteralType[]; +} + export function isBasicType(valueType: ValueType): valueType is BasicType { return valueType.kind === ValueTypeKind.basicType; } @@ -145,6 +165,10 @@ 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; +} + // TODO: Define these types to support recursive definition type BaseValue = string | number | boolean | Record | null; export type Value = BaseValue | BaseValue[] | Record; diff --git a/src/utils.ts b/src/utils.ts index 4377ab6..46115f4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,5 @@ import path from 'path'; +import { BasicTypeValue, EnumField, UnionType, ValueTypeKind } from './types'; export function capitalize(text: string): string { if (text.length === 0) { @@ -23,3 +24,60 @@ export function normalizePath(currentPath: string, basePath: string): string { const result = path.join(basePath, currentPath); return result; } + +export function uniqueNameAsMember(ownerName: string, memberName: string): string { + return `${capitalize(ownerName)}${capitalize(memberName)}`; +} + +export function uniqueNameAsMethodParameter(ownerName: string, methodName: string, parameterName: string): string { + return `${capitalize(ownerName)}${capitalize(methodName)}${capitalize(parameterName)}`; +} + +export function uniqueNameAsMethodReturnType(ownerName: string, methodName: string): string { + return `${capitalize(ownerName)}${capitalize(methodName)}ReturnType`; +} + +export function basicTypeOfUnion(union: UnionType): BasicTypeValue { + const { type } = union.value[0]; + if ('value' in type) { + return type.value; + } + return BasicTypeValue.string; +} + +export function membersOfUnion(union: UnionType): EnumField[] { + const result: EnumField[] = []; + union.value.forEach((value) => { + switch (value.type.kind) { + case ValueTypeKind.basicType: + switch (value.type.value) { + case BasicTypeValue.string: + if (typeof value.value === 'string') { + const enumField: EnumField = { + key: value.value, + value: value.value, + documentation: '', + }; + result.push(enumField); + } + break; + case BasicTypeValue.number: + if (typeof value.value === 'number') { + const enumField: EnumField = { + key: `_${value.value}`, + value: value.value, + documentation: '', + }; + result.push(enumField); + } + break; + default: + break; + } + break; + default: + break; + } + }); + return result; +} From ce95a764e7b399727cbcf22a28b43544f7a1aeea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Wed, 20 Dec 2023 16:00:41 +0800 Subject: [PATCH 02/15] rename --- src/parser/ValueParser.ts | 2 +- src/types.ts | 2 +- src/utils.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/parser/ValueParser.ts b/src/parser/ValueParser.ts index 6009936..0d9dc59 100644 --- a/src/parser/ValueParser.ts +++ b/src/parser/ValueParser.ts @@ -301,7 +301,7 @@ export class ValueParser { } const unionKind: UnionType = { kind: ValueTypeKind.unionType, - value: literalValues, + memberTypes: literalValues, }; return unionKind; } diff --git a/src/types.ts b/src/types.ts index 95a5792..7cee969 100644 --- a/src/types.ts +++ b/src/types.ts @@ -130,7 +130,7 @@ export interface LiteralType extends BaseValueType { export interface UnionType extends BaseValueType { kind: ValueTypeKind.unionType; - value: LiteralType[]; + memberTypes: LiteralType[]; } export function isBasicType(valueType: ValueType): valueType is BasicType { diff --git a/src/utils.ts b/src/utils.ts index 46115f4..0eca2a4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -38,7 +38,7 @@ export function uniqueNameAsMethodReturnType(ownerName: string, methodName: stri } export function basicTypeOfUnion(union: UnionType): BasicTypeValue { - const { type } = union.value[0]; + const { type } = union.memberTypes[0]; if ('value' in type) { return type.value; } @@ -47,7 +47,7 @@ export function basicTypeOfUnion(union: UnionType): BasicTypeValue { export function membersOfUnion(union: UnionType): EnumField[] { const result: EnumField[] = []; - union.value.forEach((value) => { + union.memberTypes.forEach((value) => { switch (value.type.kind) { case ValueTypeKind.basicType: switch (value.type.value) { From 71f1dc616ac2cac1878695490f8f59bd4a16bbdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Wed, 20 Dec 2023 17:40:32 +0800 Subject: [PATCH 03/15] reuse path for unique name & fix shared types is not in shared file --- demo/basic/generated/kotlin/BridgeTypes.kt | 19 ++++ demo/basic/generated/kotlin/IHtmlApi.kt | 81 ---------------- .../basic/generated/kotlin/IImageOptionApi.kt | 5 + demo/basic/generated/swift/IHtmlApi.swift | 55 ----------- .../generated/swift/IImageOptionApi.swift | 4 + demo/basic/generated/swift/SharedTypes.swift | 75 +++++++++++++++ demo/basic/interfaces.ts | 2 + src/generator/named-types.ts | 96 ++++++++++--------- .../KotlinValueTransformer.ts | 16 ++-- .../SwiftValueTransformer.ts | 16 ++-- .../value-transformer/ValueTransformer.ts | 4 +- src/renderer/views/InterfaceTypeView.ts | 6 +- src/renderer/views/MethodView.ts | 8 +- src/renderer/views/ModuleView.ts | 4 +- src/serializers.ts | 8 +- src/utils.ts | 8 +- 16 files changed, 189 insertions(+), 218 deletions(-) create mode 100644 demo/basic/generated/kotlin/BridgeTypes.kt create mode 100644 demo/basic/generated/swift/SharedTypes.swift diff --git a/demo/basic/generated/kotlin/BridgeTypes.kt b/demo/basic/generated/kotlin/BridgeTypes.kt new file mode 100644 index 0000000..0ed6e2e --- /dev/null +++ b/demo/basic/generated/kotlin/BridgeTypes.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2021. + * Microsoft Corporation. All rights reserved. + * + * + * This file is automatically generated + * Please DO NOT modify +*/ + +package com.microsoft.office.outlook.rooster.web.bridge + +import java.lang.reflect.Type +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import com.google.gson.annotations.SerializedName diff --git a/demo/basic/generated/kotlin/IHtmlApi.kt b/demo/basic/generated/kotlin/IHtmlApi.kt index 1de0812..38d58a5 100644 --- a/demo/basic/generated/kotlin/IHtmlApi.kt +++ b/demo/basic/generated/kotlin/IHtmlApi.kt @@ -88,87 +88,6 @@ open class IHtmlApiBridge(editor: WebEditor, gson: Gson) : JsBridge(editor, gson } } -data class OverriddenFullSize( - @JvmField val size: Float, - @JvmField val count: Int, - @JvmField val stringEnum: StringEnum, - @JvmField val numEnum: NumEnum, - @JvmField val defEnum: DefaultEnum, - @JvmField val stringUnion1: OverriddenFullSizeStringUnion1, - @JvmField val numUnion1: OverriddenFullSizeNumUnion1, - @JvmField val width: Float, - @JvmField val height: Float, - @JvmField val scale: Float, - @JvmField val member: NumEnum = NumEnum.ONE, -) - -enum class NumEnum(val value: Int) { - ONE(1), - TWO(2); - - companion object { - fun find(value: Int) = values().find { it.value == value } - } -} - -class NumEnumTypeAdapter : JsonSerializer, JsonDeserializer { - override fun serialize(obj: NumEnum, type: Type, context: JsonSerializationContext): JsonElement { - return JsonPrimitive(obj.value) - } - - override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): NumEnum? { - return NumEnum.find(json.asInt) - } -} - -enum class StringEnum { - @SerializedName("a") A, - @SerializedName("b") B -} - -enum class DefaultEnum(val value: Int) { - DEFAULT_VALUE_C(0), - DEFAULT_VALUE_D(1); - - companion object { - fun find(value: Int) = values().find { it.value == value } - } -} - -class DefaultEnumTypeAdapter : JsonSerializer, JsonDeserializer { - override fun serialize(obj: DefaultEnum, type: Type, context: JsonSerializationContext): JsonElement { - return JsonPrimitive(obj.value) - } - - override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): DefaultEnum? { - return DefaultEnum.find(json.asInt) - } -} - -enum class OverriddenFullSizeStringUnion1 { - @SerializedName("A1") A1, - @SerializedName("B1") B1 -} - -enum class OverriddenFullSizeNumUnion1(val value: Int) { - _11(11), - _21(21); - - companion object { - fun find(value: Int) = values().find { it.value == value } - } -} - -class OverriddenFullSizeNumUnion1TypeAdapter : JsonSerializer, JsonDeserializer { - override fun serialize(obj: OverriddenFullSizeNumUnion1, type: Type, context: JsonSerializationContext): JsonElement { - return JsonPrimitive(obj.value) - } - - override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): OverriddenFullSizeNumUnion1? { - return OverriddenFullSizeNumUnion1.find(json.asInt) - } -} - data class JSBaseSize( @JvmField val width: Float, @JvmField val height: Float, diff --git a/demo/basic/generated/kotlin/IImageOptionApi.kt b/demo/basic/generated/kotlin/IImageOptionApi.kt index d34e12c..5eeb896 100644 --- a/demo/basic/generated/kotlin/IImageOptionApi.kt +++ b/demo/basic/generated/kotlin/IImageOptionApi.kt @@ -20,6 +20,7 @@ interface IImageOptionApiBridge { fun getSourceOfImageWithID(id: String, callback: Callback) fun getImageDataList(callback: Callback) fun getContentBoundsOfElementWithID(id: String, callback: Callback) + fun getSize(callback: Callback) } open class IImageOptionApiBridge(editor: WebEditor, gson: Gson) : JsBridge(editor, gson, "imageOption"), IImageOptionApiBridge { @@ -51,4 +52,8 @@ open class IImageOptionApiBridge(editor: WebEditor, gson: Gson) : JsBridge(edito "id" to id )) } + + override fun getSize(callback: Callback) { + executeJsForResponse(OverriddenFullSize::class.java, "getSize", callback) + } } diff --git a/demo/basic/generated/swift/IHtmlApi.swift b/demo/basic/generated/swift/IHtmlApi.swift index 920d27f..57e73d1 100644 --- a/demo/basic/generated/swift/IHtmlApi.swift +++ b/demo/basic/generated/swift/IHtmlApi.swift @@ -87,61 +87,6 @@ public class IHtmlApi { } } -/// Example documentation for interface -public struct OverriddenFullSize: Codable { - public var size: Double - public var count: Int - public var stringEnum: StringEnum - public var numEnum: NumEnum - public var defEnum: DefaultEnum - public var stringUnion1: OverriddenFullSizeStringUnion1 - public var numUnion1: OverriddenFullSizeNumUnion1 - 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, stringUnion1: OverriddenFullSizeStringUnion1, numUnion1: OverriddenFullSizeNumUnion1, width: Double, height: Double, scale: Double) { - self.size = size - self.count = count - self.stringEnum = stringEnum - self.numEnum = numEnum - self.defEnum = defEnum - self.stringUnion1 = stringUnion1 - self.numUnion1 = numUnion1 - self.width = width - self.height = height - self.scale = scale - } -} - -public enum NumEnum: Int, Codable { - case one = 1 - case two = 2 -} - -public enum StringEnum: String, Codable { - /// Description for enum member a - case a = "a" - case b = "b" -} - -public enum DefaultEnum: Int, Codable { - case defaultValueC = 0 - case defaultValueD = 1 -} - -public enum OverriddenFullSizeStringUnion1: String, Codable { - case a1 = "A1" - case b1 = "B1" -} - -public enum OverriddenFullSizeNumUnion1: Int, Codable { - case _11 = 11 - case _21 = 21 -} - public struct BaseSize: Codable { public var width: Double public var height: Double diff --git a/demo/basic/generated/swift/IImageOptionApi.swift b/demo/basic/generated/swift/IImageOptionApi.swift index a662cb7..4ee2551 100644 --- a/demo/basic/generated/swift/IImageOptionApi.swift +++ b/demo/basic/generated/swift/IImageOptionApi.swift @@ -55,4 +55,8 @@ public class IImageOptionApi { ) jsExecutor.execute(with: "imageOption", feature: "getContentBoundsOfElementWithID", args: args, completion: completion) } + + public func getSize(completion: @escaping BridgeCompletion) { + jsExecutor.execute(with: "imageOption", feature: "getSize", args: nil, completion: completion) + } } diff --git a/demo/basic/generated/swift/SharedTypes.swift b/demo/basic/generated/swift/SharedTypes.swift new file mode 100644 index 0000000..40a9bcb --- /dev/null +++ b/demo/basic/generated/swift/SharedTypes.swift @@ -0,0 +1,75 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +// swiftformat:disable redundantRawValues +// Don't modify this file manually, it's auto generated. + +import UIKit + +/// Example documentation for interface +public struct OverriddenFullSize: Codable { + public var size: Double + public var count: Int + public var stringEnum: StringEnum + public var numEnum: NumEnum + public var defEnum: DefaultEnum + public var stringUnion1: OverriddenFullSizeMembersStringUnion1Type + public var numUnion1: OverriddenFullSizeMembersNumUnion1Type + public var foo: OverriddenFullSizeMembersFooType + 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, stringUnion1: OverriddenFullSizeMembersStringUnion1Type, numUnion1: OverriddenFullSizeMembersNumUnion1Type, foo: OverriddenFullSizeMembersFooType, width: Double, height: Double, scale: Double) { + self.size = size + self.count = count + self.stringEnum = stringEnum + self.numEnum = numEnum + self.defEnum = defEnum + self.stringUnion1 = stringUnion1 + self.numUnion1 = numUnion1 + self.foo = foo + self.width = width + self.height = height + self.scale = scale + } +} + +public enum NumEnum: Int, Codable { + case one = 1 + case two = 2 +} + +public enum StringEnum: String, Codable { + /// Description for enum member a + case a = "a" + case b = "b" +} + +public enum DefaultEnum: Int, Codable { + case defaultValueC = 0 + case defaultValueD = 1 +} + +public enum OverriddenFullSizeMembersStringUnion1Type: String, Codable { + case a1 = "A1" + case b1 = "B1" +} + +public enum OverriddenFullSizeMembersNumUnion1Type: Int, Codable { + case _11 = 11 + case _21 = 21 +} + +public struct OverriddenFullSizeMembersFooType: Codable { + public var stringField: String + public var numberField: Double + + public init(stringField: String, numberField: Double) { + self.stringField = stringField + self.numberField = numberField + } +} diff --git a/demo/basic/interfaces.ts b/demo/basic/interfaces.ts index 5ff5988..13ab19a 100644 --- a/demo/basic/interfaces.ts +++ b/demo/basic/interfaces.ts @@ -46,6 +46,7 @@ interface FullSize extends BaseSize, CustomSize { defEnum: DefaultEnum; stringUnion1: 'A1' | 'B1'; numUnion1: 11 | 21; + foo: { stringField: string } | { numberField: number }; } interface DictionaryWithAnyKey { @@ -85,4 +86,5 @@ export interface IImageOptionApi { getSourceOfImageWithID({ id }: { id: string }): string | null; getImageDataList(): string; getContentBoundsOfElementWithID({ id }: { id: string }): string | null; + getSize(): FullSize; } diff --git a/src/generator/named-types.ts b/src/generator/named-types.ts index 348211c..0a56024 100644 --- a/src/generator/named-types.ts +++ b/src/generator/named-types.ts @@ -2,9 +2,9 @@ import { capitalize, basicTypeOfUnion, membersOfUnion, - uniqueNameAsMember, - uniqueNameAsMethodParameter, - uniqueNameAsMethodReturnType, + uniquePathWithMember, + uniquePathWithMethodParameter, + uniquePathWithMethodReturnType, } from '../utils'; import { isArraryType, @@ -49,14 +49,18 @@ export type NamedTypesResult = { associatedTypes: Record fetchRootTypes(module)) - .forEach(({ valueType, uniqueName }) => { - recursiveVisitMembersType(valueType, uniqueName, (namedType) => { - if (!isInterfaceType(namedType)) { - return; - } - - namedType.name = namedType.name?.replace(/^I/, ''); - }); + .forEach(({ valueType, uniquePath }) => { + recursiveVisitMembersType( + valueType, + (namedType) => { + if (!isInterfaceType(namedType)) { + return; + } + + namedType.name = namedType.name?.replace(/^I/, ''); + }, + uniquePath + ); }); } @@ -106,25 +110,29 @@ function fetchNamedTypes(modules: Module[]): NamedTypesResult { const typeMap: Record }> = {}; modules.forEach((module) => { - fetchRootTypes(module).forEach(({ valueType, source, uniqueName }) => { - recursiveVisitMembersType(valueType, uniqueName, (membersType, path) => { - let namedType = membersType; - if (isTupleType(namedType)) { - namedType = membersType as unknown as InterfaceType; - namedType.kind = ValueTypeKind.interfaceType; - namedType.name = path; - namedType.documentation = ''; - namedType.customTags = {}; - } - - if (typeMap[namedType.name] === undefined) { - typeMap[namedType.name] = { namedType, source, associatedModules: new Set() }; - } - - const existingResult = typeMap[namedType.name]; - existingResult.associatedModules.add(module.name); - existingResult.source |= source; - }); + fetchRootTypes(module).forEach(({ valueType, source, uniquePath }) => { + recursiveVisitMembersType( + valueType, + (membersType, path) => { + let namedType = membersType; + if (isTupleType(namedType)) { + namedType = membersType as unknown as InterfaceType; + namedType.kind = ValueTypeKind.interfaceType; + namedType.name = path; + namedType.documentation = ''; + namedType.customTags = {}; + } + + if (typeMap[namedType.name] === undefined) { + typeMap[namedType.name] = { namedType, source, associatedModules: new Set() }; + } + + const existingResult = typeMap[namedType.name]; + existingResult.associatedModules.add(module.name); + existingResult.source |= source; + }, + uniquePath + ); }); }); @@ -147,18 +155,18 @@ function fetchNamedTypes(modules: Module[]): NamedTypesResult { return { associatedTypes, sharedTypes }; } -function fetchRootTypes(module: Module): { valueType: ValueType; source: ValueTypeSource; uniqueName: string }[] { +function fetchRootTypes(module: Module): { valueType: ValueType; source: ValueTypeSource; uniquePath: string }[] { const typesInMembers: ReturnType = module.members.map((field) => ({ valueType: field.type, source: ValueTypeSource.Field, - uniqueName: uniqueNameAsMember(module.name, field.name), + uniquePath: uniquePathWithMember(module.name, field.name), })); const typesInMethods: ReturnType = module.methods.flatMap((method) => method.parameters .map((parameter) => ({ valueType: parameter.type, source: ValueTypeSource.Parameter, - uniqueName: uniqueNameAsMethodParameter(module.name, method.name, parameter.name), + uniquePath: uniquePathWithMethodParameter(module.name, method.name, parameter.name), })) .concat( method.returnType @@ -166,7 +174,7 @@ function fetchRootTypes(module: Module): { valueType: ValueType; source: ValueTy { valueType: method.returnType, source: ValueTypeSource.Return, - uniqueName: uniqueNameAsMethodReturnType(module.name, method.name), + uniquePath: uniquePathWithMethodReturnType(module.name, method.name), }, ] : [] @@ -178,20 +186,14 @@ function fetchRootTypes(module: Module): { valueType: ValueType; source: ValueTy function recursiveVisitMembersType( valueType: ValueType, - uniqueName: string, visit: (membersType: NamedType | TupleType, path: string) => void, - path = '' + path: string ): void { if (isInterfaceType(valueType)) { visit(valueType, path); valueType.members.forEach((member) => { - recursiveVisitMembersType( - member.type, - `${capitalize(valueType.name)}${capitalize(member.name)}`, - visit, - `${path}${valueType.name}Members${capitalize(member.name)}Type` - ); + recursiveVisitMembersType(member.type, visit, uniquePathWithMember(valueType.name, member.name)); }); return; @@ -201,7 +203,7 @@ function recursiveVisitMembersType( visit(valueType, path); valueType.members.forEach((member) => { - recursiveVisitMembersType(member.type, uniqueName, visit, `${path}Members${capitalize(member.name)}Type`); + recursiveVisitMembersType(member.type, visit, uniquePathWithMember(path, member.name)); }); return; @@ -213,17 +215,17 @@ function recursiveVisitMembersType( } if (isArraryType(valueType)) { - recursiveVisitMembersType(valueType.elementType, uniqueName, visit, `${path}Element`); + recursiveVisitMembersType(valueType.elementType, visit, `${path}Element`); return; } if (isDictionaryType(valueType)) { - recursiveVisitMembersType(valueType.valueType, uniqueName, visit, `${path}Value`); + recursiveVisitMembersType(valueType.valueType, visit, `${path}Value`); return; } if (isOptionalType(valueType)) { - recursiveVisitMembersType(valueType.wrappedType, uniqueName, visit, `${path}`); + recursiveVisitMembersType(valueType.wrappedType, visit, `${path}`); return; } @@ -231,7 +233,7 @@ function recursiveVisitMembersType( const subType = basicTypeOfUnion(valueType); const subNamedType: EnumType = { kind: ValueTypeKind.enumType, - name: uniqueName, + name: path, // use path as unique name subType: subType === 'number' ? EnumSubType.number : EnumSubType.string, members: membersOfUnion(valueType), documentation: '', diff --git a/src/renderer/value-transformer/KotlinValueTransformer.ts b/src/renderer/value-transformer/KotlinValueTransformer.ts index a82da99..f634236 100644 --- a/src/renderer/value-transformer/KotlinValueTransformer.ts +++ b/src/renderer/value-transformer/KotlinValueTransformer.ts @@ -17,7 +17,7 @@ import { ValueTransformer } from './ValueTransformer'; export class KotlinValueTransformer implements ValueTransformer { constructor(private readonly typeNameMap: Record) {} - convertValueType(valueType: ValueType, uniqueName: string): string { + convertValueType(valueType: ValueType, uniquePath: string): string { if (isBasicType(valueType)) { switch (valueType.value) { case BasicTypeValue.string: @@ -40,7 +40,7 @@ export class KotlinValueTransformer implements ValueTransformer { } if (isArraryType(valueType)) { - return `Array<${this.convertValueType(valueType.elementType, uniqueName)}>`; + return `Array<${this.convertValueType(valueType.elementType, uniquePath)}>`; } if (isDictionaryType(valueType)) { @@ -56,11 +56,11 @@ export class KotlinValueTransformer implements ValueTransformer { throw Error('Type not exists'); } - return `Map<${keyType}, ${this.convertValueType(valueType.valueType, uniqueName)}>`; + return `Map<${keyType}, ${this.convertValueType(valueType.valueType, uniquePath)}>`; } if (isOptionalType(valueType)) { - return `${this.convertValueType(valueType.wrappedType, uniqueName)}?`; + return `${this.convertValueType(valueType.wrappedType, uniquePath)}?`; } if (isPredefinedType(valueType)) { @@ -68,18 +68,18 @@ export class KotlinValueTransformer implements ValueTransformer { } if (isUnionType(valueType)) { - return uniqueName; + return uniquePath; } throw Error('Type not handled'); } - convertNonOptionalValueType(valueType: ValueType, uniqueName: string): string { + convertNonOptionalValueType(valueType: ValueType, uniquePath: string): string { if (isOptionalType(valueType)) { - return this.convertValueType(valueType.wrappedType, uniqueName); + return this.convertValueType(valueType.wrappedType, uniquePath); } - return this.convertValueType(valueType, uniqueName); + return this.convertValueType(valueType, uniquePath); } convertValue(value: Value, type: ValueType): string { diff --git a/src/renderer/value-transformer/SwiftValueTransformer.ts b/src/renderer/value-transformer/SwiftValueTransformer.ts index 98010a4..1b7c324 100644 --- a/src/renderer/value-transformer/SwiftValueTransformer.ts +++ b/src/renderer/value-transformer/SwiftValueTransformer.ts @@ -17,7 +17,7 @@ import { ValueTransformer } from './ValueTransformer'; export class SwiftValueTransformer implements ValueTransformer { constructor(private readonly typeNameMap: Record) {} - convertValueType(valueType: ValueType, uniqueName: string): string { + convertValueType(valueType: ValueType, uniquePath: string): string { if (isBasicType(valueType)) { switch (valueType.value) { case BasicTypeValue.string: @@ -40,7 +40,7 @@ export class SwiftValueTransformer implements ValueTransformer { } if (isArraryType(valueType)) { - return `[${this.convertValueType(valueType.elementType, uniqueName)}]`; + return `[${this.convertValueType(valueType.elementType, uniquePath)}]`; } if (isDictionaryType(valueType)) { @@ -56,11 +56,11 @@ export class SwiftValueTransformer implements ValueTransformer { throw Error('Type not exists'); } - return `[${keyType}: ${this.convertValueType(valueType.valueType, uniqueName)}]`; + return `[${keyType}: ${this.convertValueType(valueType.valueType, uniquePath)}]`; } if (isOptionalType(valueType)) { - return `${this.convertValueType(valueType.wrappedType, uniqueName)}?`; + return `${this.convertValueType(valueType.wrappedType, uniquePath)}?`; } if (isPredefinedType(valueType)) { @@ -68,18 +68,18 @@ export class SwiftValueTransformer implements ValueTransformer { } if (isUnionType(valueType)) { - return uniqueName; + return uniquePath; } throw Error('Type not handled'); } - convertNonOptionalValueType(valueType: ValueType, uniqueName: string): string { + convertNonOptionalValueType(valueType: ValueType, uniquePath: string): string { if (isOptionalType(valueType)) { - return this.convertValueType(valueType.wrappedType, uniqueName); + return this.convertValueType(valueType.wrappedType, uniquePath); } - return this.convertValueType(valueType, uniqueName); + return this.convertValueType(valueType, uniquePath); } convertValue(value: Value, type: ValueType): string { diff --git a/src/renderer/value-transformer/ValueTransformer.ts b/src/renderer/value-transformer/ValueTransformer.ts index e9d7761..7306a82 100644 --- a/src/renderer/value-transformer/ValueTransformer.ts +++ b/src/renderer/value-transformer/ValueTransformer.ts @@ -1,8 +1,8 @@ import { ValueType, Value } from '../../types'; export interface ValueTransformer { - convertValueType(valueType: ValueType, uniqueName: string): string; - convertNonOptionalValueType(valueType: ValueType, uniqueName: string): string; + convertValueType(valueType: ValueType, uniquePath: string): string; + convertNonOptionalValueType(valueType: ValueType, uniquePath: string): string; convertValue(value: Value, type: ValueType): string; convertEnumKey(text: string): string; convertTypeNameFromCustomMap(name: string): string; diff --git a/src/renderer/views/InterfaceTypeView.ts b/src/renderer/views/InterfaceTypeView.ts index ac54622..754505b 100644 --- a/src/renderer/views/InterfaceTypeView.ts +++ b/src/renderer/views/InterfaceTypeView.ts @@ -1,6 +1,6 @@ import { ValueTypeSource } from '../../generator/named-types'; import { InterfaceType } from '../../types'; -import { uniqueNameAsMember } from '../../utils'; +import { uniquePathWithMember } from '../../utils'; import { getDocumentationLines } from '../utils'; import { ValueTransformer } from '../value-transformer'; @@ -20,7 +20,7 @@ export class InterfaceTypeView { const { typeName } = this; return members.map((member, index) => ({ name: member.name, - type: this.valueTransformer.convertValueType(member.type, uniqueNameAsMember(typeName, member.name)), + type: this.valueTransformer.convertValueType(member.type, uniquePathWithMember(typeName, member.name)), documentationLines: getDocumentationLines(member.documentation), last: index === members.length - 1, })); @@ -37,7 +37,7 @@ export class InterfaceTypeView { return { name: member.name, - type: this.valueTransformer.convertValueType(member.type, uniqueNameAsMember(typeName, member.name)), + type: this.valueTransformer.convertValueType(member.type, uniquePathWithMember(typeName, member.name)), value: this.valueTransformer.convertValue(member.staticValue, member.type), documentationLines: getDocumentationLines(member.documentation), }; diff --git a/src/renderer/views/MethodView.ts b/src/renderer/views/MethodView.ts index 5309a34..8319566 100644 --- a/src/renderer/views/MethodView.ts +++ b/src/renderer/views/MethodView.ts @@ -1,5 +1,5 @@ import { Method } from '../../types'; -import { uniqueNameAsMethodParameter, uniqueNameAsMethodReturnType } from '../../utils'; +import { uniquePathWithMethodParameter, uniquePathWithMethodReturnType } from '../../utils'; import { getDocumentationLines } from '../utils'; import { ValueTransformer } from '../value-transformer'; @@ -23,7 +23,7 @@ export class MethodView { name: parameter.name, type: this.valueTransformer.convertValueType( parameter.type, - uniqueNameAsMethodParameter(this.ownerName, this.methodName, parameter.name) + uniquePathWithMethodParameter(this.ownerName, this.methodName, parameter.name) ), last: index === this.method.parameters.length - 1, })); @@ -36,7 +36,7 @@ export class MethodView { return this.valueTransformer.convertValueType( this.method.returnType, - uniqueNameAsMethodReturnType(this.ownerName, this.methodName) + uniquePathWithMethodReturnType(this.ownerName, this.methodName) ); } @@ -47,7 +47,7 @@ export class MethodView { return this.valueTransformer.convertNonOptionalValueType( this.method.returnType, - uniqueNameAsMethodReturnType(this.ownerName, this.methodName) + uniquePathWithMethodReturnType(this.ownerName, this.methodName) ); } diff --git a/src/renderer/views/ModuleView.ts b/src/renderer/views/ModuleView.ts index 0caf087..538d3e2 100644 --- a/src/renderer/views/ModuleView.ts +++ b/src/renderer/views/ModuleView.ts @@ -1,5 +1,5 @@ import { Module } from '../../types'; -import { uniqueNameAsMember } from '../../utils'; +import { uniquePathWithMember } from '../../utils'; import { ValueTransformer } from '../value-transformer/ValueTransformer'; import { MethodView } from './MethodView'; import { NamedTypeView } from './index'; @@ -21,7 +21,7 @@ export class ModuleView { return members.map((member, index) => ({ name: member.name, - type: this.valueTransformer.convertValueType(member.type, uniqueNameAsMember(this.module.name, member.name)), + type: this.valueTransformer.convertValueType(member.type, uniquePathWithMember(this.module.name, member.name)), documentationLines: getDocumentationLines(member.documentation), last: index === members.length - 1, })); diff --git a/src/serializers.ts b/src/serializers.ts index a836061..57bc1fc 100644 --- a/src/serializers.ts +++ b/src/serializers.ts @@ -15,7 +15,7 @@ import { Value, isUnionType, } from './types'; -import { uniqueNameAsMember, uniqueNameAsMethodParameter, uniqueNameAsMethodReturnType } from './utils'; +import { uniquePathWithMember, uniquePathWithMethodParameter, uniquePathWithMethodReturnType } from './utils'; const keywordColor = chalk.green; const identifierColor = chalk.blue; @@ -98,7 +98,7 @@ ${namedType.members function serializeMethod(method: Method, ownerName: string): string { const serializedReturnType = method.returnType !== null - ? `: ${typeColor(serializeValueType(method.returnType, uniqueNameAsMethodReturnType(ownerName, method.name)))}` + ? `: ${typeColor(serializeValueType(method.returnType, uniquePathWithMethodReturnType(ownerName, method.name)))}` : ''; return `${serializeDocumentation(method.documentation)}${keywordColor('func')} ${identifierColor( method.name @@ -106,7 +106,7 @@ function serializeMethod(method: Method, ownerName: string): string { .map( (parameter) => `${parameter.name}: ${typeColor( - serializeValueType(parameter.type, uniqueNameAsMethodParameter(ownerName, method.name, parameter.name)) + serializeValueType(parameter.type, uniquePathWithMethodParameter(ownerName, method.name, parameter.name)) )}` ) .join(', ')})${serializedReturnType}`; @@ -116,7 +116,7 @@ function serializeField(field: Field, ownerName: string): string { const staticValue = field.staticValue !== undefined ? ` = ${serializeStaticValue(field.staticValue, field.type)}` : ''; return `${identifierColor(field.name)}: ${typeColor( - serializeValueType(field.type, uniqueNameAsMember(ownerName, field.name)) + serializeValueType(field.type, uniquePathWithMember(ownerName, field.name)) )}${staticValue}`; } diff --git a/src/utils.ts b/src/utils.ts index 0eca2a4..453a252 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -25,15 +25,15 @@ export function normalizePath(currentPath: string, basePath: string): string { return result; } -export function uniqueNameAsMember(ownerName: string, memberName: string): string { - return `${capitalize(ownerName)}${capitalize(memberName)}`; +export function uniquePathWithMember(ownerName: string, memberName: string): string { + return `${capitalize(ownerName)}Members${capitalize(memberName)}Type`; } -export function uniqueNameAsMethodParameter(ownerName: string, methodName: string, parameterName: string): string { +export function uniquePathWithMethodParameter(ownerName: string, methodName: string, parameterName: string): string { return `${capitalize(ownerName)}${capitalize(methodName)}${capitalize(parameterName)}`; } -export function uniqueNameAsMethodReturnType(ownerName: string, methodName: string): string { +export function uniquePathWithMethodReturnType(ownerName: string, methodName: string): string { return `${capitalize(ownerName)}${capitalize(methodName)}ReturnType`; } From 008f5a4b44aa01b2ce00a0fd136bc5681d44d764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Thu, 21 Dec 2023 13:09:09 +0800 Subject: [PATCH 04/15] lint --- src/generator/named-types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/generator/named-types.ts b/src/generator/named-types.ts index 0a56024..2b60822 100644 --- a/src/generator/named-types.ts +++ b/src/generator/named-types.ts @@ -1,5 +1,4 @@ import { - capitalize, basicTypeOfUnion, membersOfUnion, uniquePathWithMember, From 55f9d4103e0caf11494c0189049f8bb590d11e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Mon, 25 Dec 2023 15:24:17 +0800 Subject: [PATCH 05/15] fix: kotlin shared types is missing --- demo/basic/generated/kotlin/BridgeTypes.kt | 88 +++++++++++++++++++ demo/basic/generated/kotlin/IHtmlApi.kt | 8 +- demo/basic/generated/swift/IHtmlApi.swift | 8 +- demo/basic/interfaces.ts | 2 +- .../kotlin-named-types.mustache | 5 ++ example-templates/kotlin-named-types.mustache | 5 ++ 6 files changed, 107 insertions(+), 9 deletions(-) diff --git a/demo/basic/generated/kotlin/BridgeTypes.kt b/demo/basic/generated/kotlin/BridgeTypes.kt index 0ed6e2e..5b6b2fb 100644 --- a/demo/basic/generated/kotlin/BridgeTypes.kt +++ b/demo/basic/generated/kotlin/BridgeTypes.kt @@ -17,3 +17,91 @@ import com.google.gson.JsonPrimitive import com.google.gson.JsonSerializationContext import com.google.gson.JsonSerializer import com.google.gson.annotations.SerializedName + + +data class OverriddenFullSize( + @JvmField val size: Float, + @JvmField val count: Int, + @JvmField val stringEnum: StringEnum, + @JvmField val numEnum: NumEnum, + @JvmField val defEnum: DefaultEnum, + @JvmField val stringUnion1: OverriddenFullSizeMembersStringUnion1Type, + @JvmField val numUnion1: OverriddenFullSizeMembersNumUnion1Type, + @JvmField val foo: OverriddenFullSizeMembersFooType, + @JvmField val width: Float, + @JvmField val height: Float, + @JvmField val scale: Float, + @JvmField val member: NumEnum = NumEnum.ONE, +) + +enum class NumEnum(val value: Int) { + ONE(1), + TWO(2); + + companion object { + fun find(value: Int) = values().find { it.value == value } + } +} + +class NumEnumTypeAdapter : JsonSerializer, JsonDeserializer { + override fun serialize(obj: NumEnum, type: Type, context: JsonSerializationContext): JsonElement { + return JsonPrimitive(obj.value) + } + + override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): NumEnum? { + return NumEnum.find(json.asInt) + } +} + +enum class StringEnum { + @SerializedName("a") A, + @SerializedName("b") B +} + +enum class DefaultEnum(val value: Int) { + DEFAULT_VALUE_C(0), + DEFAULT_VALUE_D(1); + + companion object { + fun find(value: Int) = values().find { it.value == value } + } +} + +class DefaultEnumTypeAdapter : JsonSerializer, JsonDeserializer { + override fun serialize(obj: DefaultEnum, type: Type, context: JsonSerializationContext): JsonElement { + return JsonPrimitive(obj.value) + } + + override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): DefaultEnum? { + return DefaultEnum.find(json.asInt) + } +} + +enum class OverriddenFullSizeMembersStringUnion1Type { + @SerializedName("A1") A1, + @SerializedName("B1") B1 +} + +enum class OverriddenFullSizeMembersNumUnion1Type(val value: Int) { + _11(11), + _21(21); + + companion object { + fun find(value: Int) = values().find { it.value == value } + } +} + +class OverriddenFullSizeMembersNumUnion1TypeTypeAdapter : JsonSerializer, JsonDeserializer { + override fun serialize(obj: OverriddenFullSizeMembersNumUnion1Type, type: Type, context: JsonSerializationContext): JsonElement { + return JsonPrimitive(obj.value) + } + + override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): OverriddenFullSizeMembersNumUnion1Type? { + return OverriddenFullSizeMembersNumUnion1Type.find(json.asInt) + } +} + +data class OverriddenFullSizeMembersFooType( + @JvmField val stringField: String, + @JvmField val numberField: Float, +) diff --git a/demo/basic/generated/kotlin/IHtmlApi.kt b/demo/basic/generated/kotlin/IHtmlApi.kt index 38d58a5..261d309 100644 --- a/demo/basic/generated/kotlin/IHtmlApi.kt +++ b/demo/basic/generated/kotlin/IHtmlApi.kt @@ -31,7 +31,7 @@ interface IHtmlApiBridge { fun getSize(callback: Callback) fun getAliasSize(callback: Callback) fun getName(callback: Callback) - fun getAge(sex: IHtmlApiGetAgeSex, callback: Callback) + fun getAge(gender: IHtmlApiGetAgeGender, callback: Callback) fun testDictionaryWithAnyKey(dict: Map) } @@ -75,9 +75,9 @@ open class IHtmlApiBridge(editor: WebEditor, gson: Gson) : JsBridge(editor, gson executeJsForResponse(IHtmlApiGetNameReturnType::class.java, "getName", callback) } - override fun getAge(sex: IHtmlApiGetAgeSex, callback: Callback) { + override fun getAge(gender: IHtmlApiGetAgeGender, callback: Callback) { executeJsForResponse(IHtmlApiGetAgeReturnType::class.java, "getAge", callback, mapOf( - "sex" to sex + "gender" to gender )) } @@ -98,7 +98,7 @@ enum class IHtmlApiGetNameReturnType { @SerializedName("B2") B2 } -enum class IHtmlApiGetAgeSex { +enum class IHtmlApiGetAgeGender { @SerializedName("Male") MALE, @SerializedName("Female") FEMALE } diff --git a/demo/basic/generated/swift/IHtmlApi.swift b/demo/basic/generated/swift/IHtmlApi.swift index 57e73d1..3fd72f5 100644 --- a/demo/basic/generated/swift/IHtmlApi.swift +++ b/demo/basic/generated/swift/IHtmlApi.swift @@ -66,12 +66,12 @@ public class IHtmlApi { jsExecutor.execute(with: "htmlApi", feature: "getName", args: nil, completion: completion) } - public func getAge(sex: IHtmlApiGetAgeSex, completion: @escaping BridgeCompletion) { + public func getAge(gender: IHtmlApiGetAgeGender, completion: @escaping BridgeCompletion) { struct Args: Encodable { - let sex: IHtmlApiGetAgeSex + let gender: IHtmlApiGetAgeGender } let args = Args( - sex: sex + gender: gender ) jsExecutor.execute(with: "htmlApi", feature: "getAge", args: args, completion: completion) } @@ -102,7 +102,7 @@ public enum IHtmlApiGetNameReturnType: String, Codable { case b2 = "B2" } -public enum IHtmlApiGetAgeSex: String, Codable { +public enum IHtmlApiGetAgeGender: String, Codable { case male = "Male" case female = "Female" } diff --git a/demo/basic/interfaces.ts b/demo/basic/interfaces.ts index 13ab19a..84e1322 100644 --- a/demo/basic/interfaces.ts +++ b/demo/basic/interfaces.ts @@ -72,7 +72,7 @@ export interface IHtmlApi { getSize(): FullSize; getAliasSize(): AliasSize; getName(): 'A2' | 'B2'; - getAge({ sex }: { sex: 'Male' | 'Female' }): 21 | 22; + getAge({ gender }: { gender: 'Male' | 'Female' }): 21 | 22; testDictionaryWithAnyKey({ dict }: { dict: DictionaryWithAnyKey }): void; } diff --git a/demo/mini-editor/web/code-templates/kotlin-named-types.mustache b/demo/mini-editor/web/code-templates/kotlin-named-types.mustache index 0ed6e2e..c516c9c 100644 --- a/demo/mini-editor/web/code-templates/kotlin-named-types.mustache +++ b/demo/mini-editor/web/code-templates/kotlin-named-types.mustache @@ -17,3 +17,8 @@ import com.google.gson.JsonPrimitive import com.google.gson.JsonSerializationContext import com.google.gson.JsonSerializer import com.google.gson.annotations.SerializedName + +{{#.}} + +{{> kotlin-named-type}} +{{/.}} diff --git a/example-templates/kotlin-named-types.mustache b/example-templates/kotlin-named-types.mustache index 0ed6e2e..c516c9c 100644 --- a/example-templates/kotlin-named-types.mustache +++ b/example-templates/kotlin-named-types.mustache @@ -17,3 +17,8 @@ import com.google.gson.JsonPrimitive import com.google.gson.JsonSerializationContext import com.google.gson.JsonSerializer import com.google.gson.annotations.SerializedName + +{{#.}} + +{{> kotlin-named-type}} +{{/.}} From 79525c641fd3a9b3d1bb7e3828de450340ce30db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Tue, 26 Dec 2023 13:41:35 +0800 Subject: [PATCH 06/15] fix: align usage with TupleType --- src/generator/named-types.ts | 26 ++++++++++--------- .../KotlinValueTransformer.ts | 5 ---- .../SwiftValueTransformer.ts | 5 ---- src/serializers.ts | 4 --- 4 files changed, 14 insertions(+), 26 deletions(-) diff --git a/src/generator/named-types.ts b/src/generator/named-types.ts index 2b60822..be01233 100644 --- a/src/generator/named-types.ts +++ b/src/generator/named-types.ts @@ -20,6 +20,7 @@ import { ValueTypeKind, isUnionType, EnumSubType, + UnionType, } from '../types'; export const enum ValueTypeSource { @@ -113,13 +114,23 @@ function fetchNamedTypes(modules: Module[]): NamedTypesResult { recursiveVisitMembersType( valueType, (membersType, path) => { - let namedType = membersType; + let namedType = membersType as unknown as NamedType; if (isTupleType(namedType)) { namedType = membersType as unknown as InterfaceType; namedType.kind = ValueTypeKind.interfaceType; namedType.name = path; namedType.documentation = ''; namedType.customTags = {}; + } else if (isUnionType(namedType)) { + const enumType = membersType as unknown as EnumType; + const subType = basicTypeOfUnion(namedType); + + enumType.kind = ValueTypeKind.enumType; + enumType.name = path; + enumType.subType = subType === 'number' ? EnumSubType.number : EnumSubType.string; + enumType.members = membersOfUnion(namedType); + enumType.documentation = ''; + enumType.customTags = {}; } if (typeMap[namedType.name] === undefined) { @@ -185,7 +196,7 @@ function fetchRootTypes(module: Module): { valueType: ValueType; source: ValueTy function recursiveVisitMembersType( valueType: ValueType, - visit: (membersType: NamedType | TupleType, path: string) => void, + visit: (membersType: NamedType | TupleType | UnionType, path: string) => void, path: string ): void { if (isInterfaceType(valueType)) { @@ -229,16 +240,7 @@ function recursiveVisitMembersType( } if (isUnionType(valueType)) { - const subType = basicTypeOfUnion(valueType); - const subNamedType: EnumType = { - kind: ValueTypeKind.enumType, - name: path, // use path as unique name - subType: subType === 'number' ? EnumSubType.number : EnumSubType.string, - members: membersOfUnion(valueType), - documentation: '', - customTags: {}, - }; - visit(subNamedType, path); + visit(valueType, path); return; } diff --git a/src/renderer/value-transformer/KotlinValueTransformer.ts b/src/renderer/value-transformer/KotlinValueTransformer.ts index f634236..3c4644b 100644 --- a/src/renderer/value-transformer/KotlinValueTransformer.ts +++ b/src/renderer/value-transformer/KotlinValueTransformer.ts @@ -10,7 +10,6 @@ import { isPredefinedType, ValueType, Value, - isUnionType, } from '../../types'; import { ValueTransformer } from './ValueTransformer'; @@ -67,10 +66,6 @@ export class KotlinValueTransformer implements ValueTransformer { return this.typeNameMap[valueType.name] ?? valueType.name; } - if (isUnionType(valueType)) { - return uniquePath; - } - throw Error('Type not handled'); } diff --git a/src/renderer/value-transformer/SwiftValueTransformer.ts b/src/renderer/value-transformer/SwiftValueTransformer.ts index 1b7c324..ae6812d 100644 --- a/src/renderer/value-transformer/SwiftValueTransformer.ts +++ b/src/renderer/value-transformer/SwiftValueTransformer.ts @@ -10,7 +10,6 @@ import { isPredefinedType, ValueType, Value, - isUnionType, } from '../../types'; import { ValueTransformer } from './ValueTransformer'; @@ -67,10 +66,6 @@ export class SwiftValueTransformer implements ValueTransformer { return this.typeNameMap[valueType.name] ?? valueType.name; } - if (isUnionType(valueType)) { - return uniquePath; - } - throw Error('Type not handled'); } diff --git a/src/serializers.ts b/src/serializers.ts index 57bc1fc..351da88 100644 --- a/src/serializers.ts +++ b/src/serializers.ts @@ -13,7 +13,6 @@ import { Module, ValueType, Value, - isUnionType, } from './types'; import { uniquePathWithMember, uniquePathWithMethodParameter, uniquePathWithMethodReturnType } from './utils'; @@ -142,9 +141,6 @@ function serializeValueType(valueType: ValueType, uniqueTypeName: string): strin if (isPredefinedType(valueType)) { return valueType.name; } - if (isUnionType(valueType)) { - return uniqueTypeName; - } throw Error(`Unhandled value type ${JSON.stringify(valueType)}`); } From 77e63392a84529ad4f37bb6d9cb8cbca0036c501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Tue, 26 Dec 2023 13:45:26 +0800 Subject: [PATCH 07/15] style: rename members --- src/parser/ValueParser.ts | 2 +- src/types.ts | 2 +- src/utils.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/parser/ValueParser.ts b/src/parser/ValueParser.ts index 0d9dc59..8783043 100644 --- a/src/parser/ValueParser.ts +++ b/src/parser/ValueParser.ts @@ -301,7 +301,7 @@ export class ValueParser { } const unionKind: UnionType = { kind: ValueTypeKind.unionType, - memberTypes: literalValues, + members: literalValues, }; return unionKind; } diff --git a/src/types.ts b/src/types.ts index 7cee969..8c41600 100644 --- a/src/types.ts +++ b/src/types.ts @@ -130,7 +130,7 @@ export interface LiteralType extends BaseValueType { export interface UnionType extends BaseValueType { kind: ValueTypeKind.unionType; - memberTypes: LiteralType[]; + members: LiteralType[]; } export function isBasicType(valueType: ValueType): valueType is BasicType { diff --git a/src/utils.ts b/src/utils.ts index 453a252..00dcbb1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -38,7 +38,7 @@ export function uniquePathWithMethodReturnType(ownerName: string, methodName: st } export function basicTypeOfUnion(union: UnionType): BasicTypeValue { - const { type } = union.memberTypes[0]; + const { type } = union.members[0]; if ('value' in type) { return type.value; } @@ -47,7 +47,7 @@ export function basicTypeOfUnion(union: UnionType): BasicTypeValue { export function membersOfUnion(union: UnionType): EnumField[] { const result: EnumField[] = []; - union.memberTypes.forEach((value) => { + union.members.forEach((value) => { switch (value.type.kind) { case ValueTypeKind.basicType: switch (value.type.value) { From 3d7f49ddc28bc6a7f80091ad82a877b1791ec566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Tue, 26 Dec 2023 13:56:39 +0800 Subject: [PATCH 08/15] fix: remove unused content --- .../KotlinValueTransformer.ts | 14 ++++++------ .../SwiftValueTransformer.ts | 14 ++++++------ .../value-transformer/ValueTransformer.ts | 4 ++-- src/renderer/views/InterfaceTypeView.ts | 9 +++----- src/renderer/views/MethodView.ts | 22 ++++--------------- src/renderer/views/ModuleView.ts | 5 ++--- 6 files changed, 25 insertions(+), 43 deletions(-) diff --git a/src/renderer/value-transformer/KotlinValueTransformer.ts b/src/renderer/value-transformer/KotlinValueTransformer.ts index 3c4644b..bb2b904 100644 --- a/src/renderer/value-transformer/KotlinValueTransformer.ts +++ b/src/renderer/value-transformer/KotlinValueTransformer.ts @@ -16,7 +16,7 @@ import { ValueTransformer } from './ValueTransformer'; export class KotlinValueTransformer implements ValueTransformer { constructor(private readonly typeNameMap: Record) {} - convertValueType(valueType: ValueType, uniquePath: string): string { + convertValueType(valueType: ValueType): string { if (isBasicType(valueType)) { switch (valueType.value) { case BasicTypeValue.string: @@ -39,7 +39,7 @@ export class KotlinValueTransformer implements ValueTransformer { } if (isArraryType(valueType)) { - return `Array<${this.convertValueType(valueType.elementType, uniquePath)}>`; + return `Array<${this.convertValueType(valueType.elementType)}>`; } if (isDictionaryType(valueType)) { @@ -55,11 +55,11 @@ export class KotlinValueTransformer implements ValueTransformer { throw Error('Type not exists'); } - return `Map<${keyType}, ${this.convertValueType(valueType.valueType, uniquePath)}>`; + return `Map<${keyType}, ${this.convertValueType(valueType.valueType)}>`; } if (isOptionalType(valueType)) { - return `${this.convertValueType(valueType.wrappedType, uniquePath)}?`; + return `${this.convertValueType(valueType.wrappedType)}?`; } if (isPredefinedType(valueType)) { @@ -69,12 +69,12 @@ export class KotlinValueTransformer implements ValueTransformer { throw Error('Type not handled'); } - convertNonOptionalValueType(valueType: ValueType, uniquePath: string): string { + convertNonOptionalValueType(valueType: ValueType): string { if (isOptionalType(valueType)) { - return this.convertValueType(valueType.wrappedType, uniquePath); + return this.convertValueType(valueType.wrappedType); } - return this.convertValueType(valueType, uniquePath); + return this.convertValueType(valueType); } convertValue(value: Value, type: ValueType): string { diff --git a/src/renderer/value-transformer/SwiftValueTransformer.ts b/src/renderer/value-transformer/SwiftValueTransformer.ts index ae6812d..891af84 100644 --- a/src/renderer/value-transformer/SwiftValueTransformer.ts +++ b/src/renderer/value-transformer/SwiftValueTransformer.ts @@ -16,7 +16,7 @@ import { ValueTransformer } from './ValueTransformer'; export class SwiftValueTransformer implements ValueTransformer { constructor(private readonly typeNameMap: Record) {} - convertValueType(valueType: ValueType, uniquePath: string): string { + convertValueType(valueType: ValueType): string { if (isBasicType(valueType)) { switch (valueType.value) { case BasicTypeValue.string: @@ -39,7 +39,7 @@ export class SwiftValueTransformer implements ValueTransformer { } if (isArraryType(valueType)) { - return `[${this.convertValueType(valueType.elementType, uniquePath)}]`; + return `[${this.convertValueType(valueType.elementType)}]`; } if (isDictionaryType(valueType)) { @@ -55,11 +55,11 @@ export class SwiftValueTransformer implements ValueTransformer { throw Error('Type not exists'); } - return `[${keyType}: ${this.convertValueType(valueType.valueType, uniquePath)}]`; + return `[${keyType}: ${this.convertValueType(valueType.valueType)}]`; } if (isOptionalType(valueType)) { - return `${this.convertValueType(valueType.wrappedType, uniquePath)}?`; + return `${this.convertValueType(valueType.wrappedType)}?`; } if (isPredefinedType(valueType)) { @@ -69,12 +69,12 @@ export class SwiftValueTransformer implements ValueTransformer { throw Error('Type not handled'); } - convertNonOptionalValueType(valueType: ValueType, uniquePath: string): string { + convertNonOptionalValueType(valueType: ValueType): string { if (isOptionalType(valueType)) { - return this.convertValueType(valueType.wrappedType, uniquePath); + return this.convertValueType(valueType.wrappedType); } - return this.convertValueType(valueType, uniquePath); + return this.convertValueType(valueType); } convertValue(value: Value, type: ValueType): string { diff --git a/src/renderer/value-transformer/ValueTransformer.ts b/src/renderer/value-transformer/ValueTransformer.ts index 7306a82..6ced0b6 100644 --- a/src/renderer/value-transformer/ValueTransformer.ts +++ b/src/renderer/value-transformer/ValueTransformer.ts @@ -1,8 +1,8 @@ import { ValueType, Value } from '../../types'; export interface ValueTransformer { - convertValueType(valueType: ValueType, uniquePath: string): string; - convertNonOptionalValueType(valueType: ValueType, uniquePath: string): string; + convertValueType(valueType: ValueType): string; + convertNonOptionalValueType(valueType: ValueType): string; convertValue(value: Value, type: ValueType): string; convertEnumKey(text: string): string; convertTypeNameFromCustomMap(name: string): string; diff --git a/src/renderer/views/InterfaceTypeView.ts b/src/renderer/views/InterfaceTypeView.ts index 754505b..47bf025 100644 --- a/src/renderer/views/InterfaceTypeView.ts +++ b/src/renderer/views/InterfaceTypeView.ts @@ -1,6 +1,5 @@ import { ValueTypeSource } from '../../generator/named-types'; import { InterfaceType } from '../../types'; -import { uniquePathWithMember } from '../../utils'; import { getDocumentationLines } from '../utils'; import { ValueTransformer } from '../value-transformer'; @@ -12,22 +11,20 @@ export class InterfaceTypeView { ) {} get typeName(): string { - return this.valueTransformer.convertValueType(this.interfaceType, ''); + return this.valueTransformer.convertValueType(this.interfaceType); } get members(): { name: string; type: string; documentationLines: string[]; last: boolean }[] { const members = this.interfaceType.members.filter((member) => member.staticValue === undefined); - const { typeName } = this; return members.map((member, index) => ({ name: member.name, - type: this.valueTransformer.convertValueType(member.type, uniquePathWithMember(typeName, member.name)), + type: this.valueTransformer.convertValueType(member.type), documentationLines: getDocumentationLines(member.documentation), last: index === members.length - 1, })); } get staticMembers(): { name: string; type: string; value: string; documentationLines: string[] }[] { - const { typeName } = this; return this.interfaceType.members .filter((member) => member.staticValue !== undefined) .map((member) => { @@ -37,7 +34,7 @@ export class InterfaceTypeView { return { name: member.name, - type: this.valueTransformer.convertValueType(member.type, uniquePathWithMember(typeName, member.name)), + type: this.valueTransformer.convertValueType(member.type), value: this.valueTransformer.convertValue(member.staticValue, member.type), documentationLines: getDocumentationLines(member.documentation), }; diff --git a/src/renderer/views/MethodView.ts b/src/renderer/views/MethodView.ts index 8319566..bea827e 100644 --- a/src/renderer/views/MethodView.ts +++ b/src/renderer/views/MethodView.ts @@ -1,14 +1,9 @@ import { Method } from '../../types'; -import { uniquePathWithMethodParameter, uniquePathWithMethodReturnType } from '../../utils'; import { getDocumentationLines } from '../utils'; import { ValueTransformer } from '../value-transformer'; export class MethodView { - constructor( - private readonly method: Method, - private readonly valueTransformer: ValueTransformer, - private readonly ownerName: string - ) {} + constructor(private readonly method: Method, private readonly valueTransformer: ValueTransformer) {} get methodName(): string { return this.method.name; @@ -21,10 +16,7 @@ export class MethodView { get parameters(): { name: string; type: string; last: boolean }[] { return this.method.parameters.map((parameter, index) => ({ name: parameter.name, - type: this.valueTransformer.convertValueType( - parameter.type, - uniquePathWithMethodParameter(this.ownerName, this.methodName, parameter.name) - ), + type: this.valueTransformer.convertValueType(parameter.type), last: index === this.method.parameters.length - 1, })); } @@ -34,10 +26,7 @@ export class MethodView { return null; } - return this.valueTransformer.convertValueType( - this.method.returnType, - uniquePathWithMethodReturnType(this.ownerName, this.methodName) - ); + return this.valueTransformer.convertValueType(this.method.returnType); } get nonOptionalReturnType(): string | null { @@ -45,10 +34,7 @@ export class MethodView { return null; } - return this.valueTransformer.convertNonOptionalValueType( - this.method.returnType, - uniquePathWithMethodReturnType(this.ownerName, this.methodName) - ); + return this.valueTransformer.convertNonOptionalValueType(this.method.returnType); } get documentationLines(): string[] { diff --git a/src/renderer/views/ModuleView.ts b/src/renderer/views/ModuleView.ts index 538d3e2..671d90e 100644 --- a/src/renderer/views/ModuleView.ts +++ b/src/renderer/views/ModuleView.ts @@ -1,5 +1,4 @@ import { Module } from '../../types'; -import { uniquePathWithMember } from '../../utils'; import { ValueTransformer } from '../value-transformer/ValueTransformer'; import { MethodView } from './MethodView'; import { NamedTypeView } from './index'; @@ -21,14 +20,14 @@ export class ModuleView { return members.map((member, index) => ({ name: member.name, - type: this.valueTransformer.convertValueType(member.type, uniquePathWithMember(this.module.name, member.name)), + type: this.valueTransformer.convertValueType(member.type), documentationLines: getDocumentationLines(member.documentation), last: index === members.length - 1, })); } get methods(): MethodView[] { - return this.module.methods.map((method) => new MethodView(method, this.valueTransformer, this.module.name)); + return this.module.methods.map((method) => new MethodView(method, this.valueTransformer)); } get exportedInterfaceBases(): Record { From 5e287029437e9d32f32b123c92b337dae785aa05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Tue, 26 Dec 2023 14:12:26 +0800 Subject: [PATCH 09/15] feature: add nullable check --- demo/basic/generated/kotlin/BridgeTypes.kt | 10 ++++++++-- demo/basic/generated/swift/SharedTypes.swift | 15 +++++++++++---- demo/basic/interfaces.ts | 3 ++- src/parser/ValueParser.ts | 8 ++++++++ 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/demo/basic/generated/kotlin/BridgeTypes.kt b/demo/basic/generated/kotlin/BridgeTypes.kt index 5b6b2fb..e537395 100644 --- a/demo/basic/generated/kotlin/BridgeTypes.kt +++ b/demo/basic/generated/kotlin/BridgeTypes.kt @@ -25,7 +25,8 @@ data class OverriddenFullSize( @JvmField val stringEnum: StringEnum, @JvmField val numEnum: NumEnum, @JvmField val defEnum: DefaultEnum, - @JvmField val stringUnion1: OverriddenFullSizeMembersStringUnion1Type, + @JvmField val stringUnion: OverriddenFullSizeMembersStringUnionType, + @JvmField val nullableStringUnion: OverriddenFullSizeMembersNullableStringUnionType?, @JvmField val numUnion1: OverriddenFullSizeMembersNumUnion1Type, @JvmField val foo: OverriddenFullSizeMembersFooType, @JvmField val width: Float, @@ -77,7 +78,12 @@ class DefaultEnumTypeAdapter : JsonSerializer, JsonDeserializer Date: Tue, 26 Dec 2023 14:19:13 +0800 Subject: [PATCH 10/15] fix: revert unused content --- src/generator/CodeGenerator.ts | 2 +- src/renderer/views/InterfaceTypeView.ts | 1 + src/serializers.ts | 46 ++++++++----------------- 3 files changed, 17 insertions(+), 32 deletions(-) diff --git a/src/generator/CodeGenerator.ts b/src/generator/CodeGenerator.ts index 47b5fa4..0e8c937 100644 --- a/src/generator/CodeGenerator.ts +++ b/src/generator/CodeGenerator.ts @@ -76,7 +76,7 @@ export class CodeGenerator { printSharedTypes(sharedTypes: NamedTypeInfo[]): void { console.log('Shared named types:\n'); - console.log(sharedTypes.map((namedType) => serializeNamedType(namedType.type, '')).join('\n\n')); + console.log(sharedTypes.map((namedType) => serializeNamedType(namedType.type)).join('\n\n')); } renderModules(modules: ParsedModule[], options: RenderOptions): void { diff --git a/src/renderer/views/InterfaceTypeView.ts b/src/renderer/views/InterfaceTypeView.ts index 47bf025..02a55db 100644 --- a/src/renderer/views/InterfaceTypeView.ts +++ b/src/renderer/views/InterfaceTypeView.ts @@ -16,6 +16,7 @@ export class InterfaceTypeView { get members(): { name: string; type: string; documentationLines: string[]; last: boolean }[] { const members = this.interfaceType.members.filter((member) => member.staticValue === undefined); + return members.map((member, index) => ({ name: member.name, type: this.valueTransformer.convertValueType(member.type), diff --git a/src/serializers.ts b/src/serializers.ts index 351da88..7ee1dea 100644 --- a/src/serializers.ts +++ b/src/serializers.ts @@ -14,7 +14,6 @@ import { ValueType, Value, } from './types'; -import { uniquePathWithMember, uniquePathWithMethodParameter, uniquePathWithMethodReturnType } from './utils'; const keywordColor = chalk.green; const identifierColor = chalk.blue; @@ -23,7 +22,7 @@ const valueColor = chalk.cyan; const documentationColor = chalk.gray; export function serializeModule(module: Module, associatedTypes: NamedType[]): string { - const serializedAssociatedTypes = associatedTypes.map((associatedType) => serializeNamedType(associatedType, '')); + const serializedAssociatedTypes = associatedTypes.map((associatedType) => serializeNamedType(associatedType)); const customTags = Object.keys(module.customTags).length > 0 ? `Custom tags: ${JSON.stringify(module.customTags)}\n` : ''; @@ -31,10 +30,7 @@ export function serializeModule(module: Module, associatedTypes: NamedType[]): s module.name } { ${module.members - .map( - (member) => - `${serializeDocumentation(member.documentation)}${keywordColor('var')} ${serializeField(member, module.name)}` - ) + .map((member) => `${serializeDocumentation(member.documentation)}${keywordColor('var')} ${serializeField(member)}`) .join('\n') .split('\n') .map((line) => ` ${line}`) @@ -42,7 +38,7 @@ ${module.members ${module.methods .map((method) => - serializeMethod(method, module.name) + serializeMethod(method) .split('\n') .map((line) => ` ${line}`) .join('\n') @@ -59,7 +55,7 @@ ${module.methods }`; } -export function serializeNamedType(namedType: NamedType, ownerName: string): string { +export function serializeNamedType(namedType: NamedType): string { const customTags = Object.keys(namedType.customTags).length > 0 ? `Custom tags: ${JSON.stringify(namedType.customTags)}\n` : ''; @@ -68,10 +64,7 @@ export function serializeNamedType(namedType: NamedType, ownerName: string): str 'Type' )} ${namedType.name} { ${namedType.members - .map( - (member) => - `${serializeDocumentation(member.documentation)}${keywordColor('var')} ${serializeField(member, ownerName)}` - ) + .map((member) => `${serializeDocumentation(member.documentation)}${keywordColor('var')} ${serializeField(member)}`) .join('\n') .split('\n') .map((line) => ` ${line}`) @@ -94,32 +87,23 @@ ${namedType.members }`; } -function serializeMethod(method: Method, ownerName: string): string { +function serializeMethod(method: Method): string { const serializedReturnType = - method.returnType !== null - ? `: ${typeColor(serializeValueType(method.returnType, uniquePathWithMethodReturnType(ownerName, method.name)))}` - : ''; + method.returnType !== null ? `: ${typeColor(serializeValueType(method.returnType))}` : ''; return `${serializeDocumentation(method.documentation)}${keywordColor('func')} ${identifierColor( method.name )}(${method.parameters - .map( - (parameter) => - `${parameter.name}: ${typeColor( - serializeValueType(parameter.type, uniquePathWithMethodParameter(ownerName, method.name, parameter.name)) - )}` - ) + .map((parameter) => `${parameter.name}: ${typeColor(serializeValueType(parameter.type))}`) .join(', ')})${serializedReturnType}`; } -function serializeField(field: Field, ownerName: string): string { +function serializeField(field: Field): string { const staticValue = field.staticValue !== undefined ? ` = ${serializeStaticValue(field.staticValue, field.type)}` : ''; - return `${identifierColor(field.name)}: ${typeColor( - serializeValueType(field.type, uniquePathWithMember(ownerName, field.name)) - )}${staticValue}`; + return `${identifierColor(field.name)}: ${typeColor(serializeValueType(field.type))}${staticValue}`; } -function serializeValueType(valueType: ValueType, uniqueTypeName: string): string { +function serializeValueType(valueType: ValueType): string { if (isBasicType(valueType)) { return valueType.value; } @@ -130,13 +114,13 @@ function serializeValueType(valueType: ValueType, uniqueTypeName: string): strin return valueType.name; } if (isArraryType(valueType)) { - return `[${serializeValueType(valueType.elementType, uniqueTypeName)}]`; + return `[${serializeValueType(valueType.elementType)}]`; } if (isDictionaryType(valueType)) { - return `[${valueType.keyType}: ${serializeValueType(valueType.valueType, uniqueTypeName)}]`; + return `[${valueType.keyType}: ${serializeValueType(valueType.valueType)}]`; } if (isOptionalType(valueType)) { - return `${serializeValueType(valueType.wrappedType, uniqueTypeName)}?`; + return `${serializeValueType(valueType.wrappedType)}?`; } if (isPredefinedType(valueType)) { return valueType.name; @@ -165,4 +149,4 @@ ${documentation .join('\n')} */ `); -} \ No newline at end of file +} From 99c68d8d0077ce5eb46ce924fb5d20ea807314d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Tue, 26 Dec 2023 15:10:34 +0800 Subject: [PATCH 11/15] doc: update document --- documentation/interface-guide.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/documentation/interface-guide.md b/documentation/interface-guide.md index 044234d..19e94b7 100644 --- a/documentation/interface-guide.md +++ b/documentation/interface-guide.md @@ -146,7 +146,7 @@ Arries defined like `string[]` and `Array` are supported. The element ca The support for union types is limited. Only these scenrios are supported: - Any supported value type union with `null` or/and `undefined` to specify optional type -- Union two interfaces or object literals to a new object literal +- Union two interfaces or object literals to a new object literal or union values of the same literal type to enum in the target language, e.g. string, number. - Combination of the above two cases Any other union types would result in an error. @@ -156,6 +156,12 @@ Any other union types would result in an error. string | null string | undefined string | null | undefined +'1' | '2' | null; + +// allowed: literal values +'1' | '2'; +1 | 2; + interface StringFieldInterface { stringField: string; From 73b868d90e7cceec782ce2cea3aa0337ab4c7df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Tue, 26 Dec 2023 15:47:53 +0800 Subject: [PATCH 12/15] fix: remove kind from LiteralType --- src/parser/ValueParser.ts | 3 --- src/types.ts | 6 ++---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/parser/ValueParser.ts b/src/parser/ValueParser.ts index 3d02b6b..7621a8b 100644 --- a/src/parser/ValueParser.ts +++ b/src/parser/ValueParser.ts @@ -690,7 +690,6 @@ export class ValueParser { if (ts.isLiteralTypeNode(typeNode)) { if (ts.isNumericLiteral(typeNode.literal)) { return { - kind: ValueTypeKind.literalType, type: { kind: ValueTypeKind.basicType, value: BasicTypeValue.number, @@ -701,7 +700,6 @@ export class ValueParser { if (ts.isStringLiteral(typeNode.literal)) { return { - kind: ValueTypeKind.literalType, type: { kind: ValueTypeKind.basicType, value: BasicTypeValue.string, @@ -731,7 +729,6 @@ export class ValueParser { throw Error(`Invalid enum member ${typeName}`); } return { - kind: ValueTypeKind.literalType, type: enumType, value: referencedNode.name.getText(), }; diff --git a/src/types.ts b/src/types.ts index 8c41600..16205f5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -31,7 +31,6 @@ export type NonEmptyType = | ArrayType | DictionaryType | PredefinedType - | LiteralType | UnionType; export enum ValueTypeKind { @@ -43,7 +42,6 @@ export enum ValueTypeKind { dictionaryType = 'dictionaryType', optionalType = 'optionalType', predefinedType = 'predefinedType', - literalType = 'literalType', unionType = 'unionType', } @@ -117,8 +115,8 @@ export interface PredefinedType extends BaseValueType { name: string; } -export interface LiteralType extends BaseValueType { - kind: ValueTypeKind.literalType; +/// inferace for parseLiteralNode in src/parser/ValueParser.ts +export interface LiteralType { type: | { kind: ValueTypeKind.basicType; From 9c458cbe9015be7238fbd9b22dda9f35a5d360fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Tue, 26 Dec 2023 16:06:31 +0800 Subject: [PATCH 13/15] fix: remove type cast of namedType --- src/generator/named-types.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/generator/named-types.ts b/src/generator/named-types.ts index be01233..8c383cf 100644 --- a/src/generator/named-types.ts +++ b/src/generator/named-types.ts @@ -114,7 +114,7 @@ function fetchNamedTypes(modules: Module[]): NamedTypesResult { recursiveVisitMembersType( valueType, (membersType, path) => { - let namedType = membersType as unknown as NamedType; + let namedType = membersType; if (isTupleType(namedType)) { namedType = membersType as unknown as InterfaceType; namedType.kind = ValueTypeKind.interfaceType; @@ -122,15 +122,16 @@ function fetchNamedTypes(modules: Module[]): NamedTypesResult { namedType.documentation = ''; namedType.customTags = {}; } else if (isUnionType(namedType)) { - const enumType = membersType as unknown as EnumType; const subType = basicTypeOfUnion(namedType); + const members = membersOfUnion(namedType); - enumType.kind = ValueTypeKind.enumType; - enumType.name = path; - enumType.subType = subType === 'number' ? EnumSubType.number : EnumSubType.string; - enumType.members = membersOfUnion(namedType); - enumType.documentation = ''; - enumType.customTags = {}; + namedType = membersType as unknown as EnumType; + namedType.kind = ValueTypeKind.enumType; + namedType.name = path; + namedType.subType = subType === 'number' ? EnumSubType.number : EnumSubType.string; + namedType.members = members; + namedType.documentation = ''; + namedType.customTags = {}; } if (typeMap[namedType.name] === undefined) { From 3f602d0609bbb28c8c67742e6349929d15c09a80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Tue, 26 Dec 2023 16:26:21 +0800 Subject: [PATCH 14/15] fix: revert LiteralType from parseLiteralNode --- src/parser/ValueParser.ts | 27 +++++++++++--------- src/types.ts | 12 +++------ src/utils.ts | 52 ++++++++++++++++----------------------- 3 files changed, 39 insertions(+), 52 deletions(-) diff --git a/src/parser/ValueParser.ts b/src/parser/ValueParser.ts index 7621a8b..31edff7 100644 --- a/src/parser/ValueParser.ts +++ b/src/parser/ValueParser.ts @@ -20,9 +20,10 @@ import { isTupleType, EnumField, isBasicType, - LiteralType, UnionType, OptionalType, + Value, + UnionLiteralType, } from '../types'; import { isUndefinedOrNull, parseTypeJSDocTags } from './utils'; import { ParserLogger } from '../logger/ParserLogger'; @@ -245,7 +246,7 @@ export class ValueParser { let nullable = false; let valueType: ValueType | undefined; - const literalValues: LiteralType[] = []; + const literalValues: UnionLiteralType[] = []; node.types.forEach((typeNode) => { if (isUndefinedOrNull(typeNode)) { @@ -254,7 +255,16 @@ export class ValueParser { } const literalKind = this.parseLiteralNode(typeNode); if (literalKind !== null) { - literalValues.push(literalKind); + if ( + literalKind.type.kind === ValueTypeKind.basicType && + (literalKind.type.value === BasicTypeValue.string || literalKind.type.value === BasicTypeValue.number) + ) { + literalValues.push({ + type: literalKind.type.value, + value: literalKind.value, + }); + return; + } return; } @@ -285,14 +295,7 @@ export class ValueParser { }); if (literalValues.length > 0) { - const kindSet = new Set( - literalValues.map((obj) => { - if ('value' in obj.type) { - return obj.type.value; - } - return null; - }) - ); + const kindSet = new Set(literalValues.map((obj) => obj.type)); if (kindSet.size !== 1) { throw new ValueParserError( @@ -686,7 +689,7 @@ export class ValueParser { }; } - private parseLiteralNode(typeNode: ts.TypeNode): LiteralType | null { + private parseLiteralNode(typeNode: ts.TypeNode): { type: ValueType; value: Value } | null { if (ts.isLiteralTypeNode(typeNode)) { if (ts.isNumericLiteral(typeNode.literal)) { return { diff --git a/src/types.ts b/src/types.ts index 16205f5..97ebd0d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -115,20 +115,14 @@ export interface PredefinedType extends BaseValueType { name: string; } -/// inferace for parseLiteralNode in src/parser/ValueParser.ts -export interface LiteralType { - type: - | { - kind: ValueTypeKind.basicType; - value: BasicTypeValue.string | BasicTypeValue.number | BasicTypeValue.boolean; - } - | EnumType; +export interface UnionLiteralType { + type: BasicTypeValue.string | BasicTypeValue.number; value: Value; } export interface UnionType extends BaseValueType { kind: ValueTypeKind.unionType; - members: LiteralType[]; + members: UnionLiteralType[]; } export function isBasicType(valueType: ValueType): valueType is BasicType { diff --git a/src/utils.ts b/src/utils.ts index 00dcbb1..3662df9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,5 @@ import path from 'path'; -import { BasicTypeValue, EnumField, UnionType, ValueTypeKind } from './types'; +import { BasicTypeValue, EnumField, UnionType } from './types'; export function capitalize(text: string): string { if (text.length === 0) { @@ -38,41 +38,31 @@ export function uniquePathWithMethodReturnType(ownerName: string, methodName: st } export function basicTypeOfUnion(union: UnionType): BasicTypeValue { - const { type } = union.members[0]; - if ('value' in type) { - return type.value; - } - return BasicTypeValue.string; + return union.members[0].type; } export function membersOfUnion(union: UnionType): EnumField[] { const result: EnumField[] = []; union.members.forEach((value) => { - switch (value.type.kind) { - case ValueTypeKind.basicType: - switch (value.type.value) { - case BasicTypeValue.string: - if (typeof value.value === 'string') { - const enumField: EnumField = { - key: value.value, - value: value.value, - documentation: '', - }; - result.push(enumField); - } - break; - case BasicTypeValue.number: - if (typeof value.value === 'number') { - const enumField: EnumField = { - key: `_${value.value}`, - value: value.value, - documentation: '', - }; - result.push(enumField); - } - break; - default: - break; + switch (value.type) { + case BasicTypeValue.string: + if (typeof value.value === 'string') { + const enumField: EnumField = { + key: value.value, + value: value.value, + documentation: '', + }; + result.push(enumField); + } + break; + case BasicTypeValue.number: + if (typeof value.value === 'number') { + const enumField: EnumField = { + key: `_${value.value}`, + value: value.value, + documentation: '', + }; + result.push(enumField); } break; default: From 8c2108ceeb44361444d12fd7f15d9e485902bda3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=BB=9B=E6=B7=B3=E5=8D=8E?= Date: Tue, 9 Jan 2024 12:09:00 +0800 Subject: [PATCH 15/15] fix: Add support for strings that look like numbers --- demo/basic/generated/kotlin/BridgeTypes.kt | 6 ++++ demo/basic/generated/swift/SharedTypes.swift | 9 +++++- demo/basic/interfaces.ts | 1 + src/parser/ValueParser.ts | 17 ++++++++-- src/types.ts | 6 ++-- src/utils.ts | 34 ++++++-------------- 6 files changed, 42 insertions(+), 31 deletions(-) diff --git a/demo/basic/generated/kotlin/BridgeTypes.kt b/demo/basic/generated/kotlin/BridgeTypes.kt index e537395..58d0c2d 100644 --- a/demo/basic/generated/kotlin/BridgeTypes.kt +++ b/demo/basic/generated/kotlin/BridgeTypes.kt @@ -26,6 +26,7 @@ data class OverriddenFullSize( @JvmField val numEnum: NumEnum, @JvmField val defEnum: DefaultEnum, @JvmField val stringUnion: OverriddenFullSizeMembersStringUnionType, + @JvmField val numberStringUnion: OverriddenFullSizeMembersNumberStringUnionType, @JvmField val nullableStringUnion: OverriddenFullSizeMembersNullableStringUnionType?, @JvmField val numUnion1: OverriddenFullSizeMembersNumUnion1Type, @JvmField val foo: OverriddenFullSizeMembersFooType, @@ -83,6 +84,11 @@ enum class OverriddenFullSizeMembersStringUnionType { @SerializedName("B1") B1 } +enum class OverriddenFullSizeMembersNumberStringUnionType { + @SerializedName("11") _11, + @SerializedName("21") _21 +} + enum class OverriddenFullSizeMembersNullableStringUnionType { @SerializedName("A1") A1, @SerializedName("B1") B1 diff --git a/demo/basic/generated/swift/SharedTypes.swift b/demo/basic/generated/swift/SharedTypes.swift index 1ae6952..1c329a0 100644 --- a/demo/basic/generated/swift/SharedTypes.swift +++ b/demo/basic/generated/swift/SharedTypes.swift @@ -15,6 +15,7 @@ public struct OverriddenFullSize: Codable { public var numEnum: NumEnum public var defEnum: DefaultEnum public var stringUnion: OverriddenFullSizeMembersStringUnionType + public var numberStringUnion: OverriddenFullSizeMembersNumberStringUnionType public var nullableStringUnion: OverriddenFullSizeMembersNullableStringUnionType? public var numUnion1: OverriddenFullSizeMembersNumUnion1Type public var foo: OverriddenFullSizeMembersFooType @@ -24,13 +25,14 @@ public struct OverriddenFullSize: Codable { /// Example documentation for member private var member: NumEnum = .one - public init(size: Double, count: Int, stringEnum: StringEnum, numEnum: NumEnum, defEnum: DefaultEnum, stringUnion: OverriddenFullSizeMembersStringUnionType, 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, width: Double, height: Double, scale: Double) { self.size = size self.count = count self.stringEnum = stringEnum self.numEnum = numEnum self.defEnum = defEnum self.stringUnion = stringUnion + self.numberStringUnion = numberStringUnion self.nullableStringUnion = nullableStringUnion self.numUnion1 = numUnion1 self.foo = foo @@ -61,6 +63,11 @@ public enum OverriddenFullSizeMembersStringUnionType: String, Codable { case b1 = "B1" } +public enum OverriddenFullSizeMembersNumberStringUnionType: String, Codable { + case _11 = "11" + case _21 = "21" +} + public enum OverriddenFullSizeMembersNullableStringUnionType: String, Codable { case a1 = "A1" case b1 = "B1" diff --git a/demo/basic/interfaces.ts b/demo/basic/interfaces.ts index f74c47c..a053f57 100644 --- a/demo/basic/interfaces.ts +++ b/demo/basic/interfaces.ts @@ -45,6 +45,7 @@ interface FullSize extends BaseSize, CustomSize { numEnum: NumEnum; defEnum: DefaultEnum; stringUnion: 'A1' | 'B1'; + numberStringUnion: '11' | '21'; nullableStringUnion: 'A1' | 'B1' | null; numUnion1: 11 | 21; foo: { stringField: string } | { numberField: number }; diff --git a/src/parser/ValueParser.ts b/src/parser/ValueParser.ts index 31edff7..c9cc4d5 100644 --- a/src/parser/ValueParser.ts +++ b/src/parser/ValueParser.ts @@ -246,7 +246,10 @@ export class ValueParser { let nullable = false; let valueType: ValueType | undefined; - const literalValues: UnionLiteralType[] = []; + const literalValues: { + type: BasicTypeValue.string | BasicTypeValue.number; + value: Value; + }[] = []; node.types.forEach((typeNode) => { if (isUndefinedOrNull(typeNode)) { @@ -303,9 +306,19 @@ export class ValueParser { 'Union type must contain only one supported literal type' ); } + const members: UnionLiteralType[] = []; + literalValues.forEach((obj) => { + if (typeof obj.value === 'string') { + members.push(obj.value); + } + if (typeof obj.value === 'number') { + members.push(obj.value); + } + }); const unionKind: UnionType = { kind: ValueTypeKind.unionType, - members: literalValues, + memberType: literalValues[0].type, + members, }; if (nullable) { const optionalType: OptionalType = { diff --git a/src/types.ts b/src/types.ts index 97ebd0d..8fb5724 100644 --- a/src/types.ts +++ b/src/types.ts @@ -115,13 +115,11 @@ export interface PredefinedType extends BaseValueType { name: string; } -export interface UnionLiteralType { - type: BasicTypeValue.string | BasicTypeValue.number; - value: Value; -} +export type UnionLiteralType = string | number; export interface UnionType extends BaseValueType { kind: ValueTypeKind.unionType; + memberType: BasicTypeValue.string | BasicTypeValue.number; members: UnionLiteralType[]; } diff --git a/src/utils.ts b/src/utils.ts index 3662df9..bda2a3d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -38,36 +38,22 @@ export function uniquePathWithMethodReturnType(ownerName: string, methodName: st } export function basicTypeOfUnion(union: UnionType): BasicTypeValue { - return union.members[0].type; + return union.memberType; } export function membersOfUnion(union: UnionType): EnumField[] { const result: EnumField[] = []; union.members.forEach((value) => { - switch (value.type) { - case BasicTypeValue.string: - if (typeof value.value === 'string') { - const enumField: EnumField = { - key: value.value, - value: value.value, - documentation: '', - }; - result.push(enumField); - } - break; - case BasicTypeValue.number: - if (typeof value.value === 'number') { - const enumField: EnumField = { - key: `_${value.value}`, - value: value.value, - documentation: '', - }; - result.push(enumField); - } - break; - default: - break; + let key = `${value}`; + if (!Number.isNaN(Number(value))) { + key = `_${key}`; } + const enumField: EnumField = { + key, + value, + documentation: '', + }; + result.push(enumField); }); return result; }