From f02c3c0c383e2553c93c9e4173a967f271ef45ba Mon Sep 17 00:00:00 2001 From: York Yao Date: Tue, 20 Oct 2020 22:19:15 +0800 Subject: [PATCH] feat: improve generated root type --- demo/root-type.ts | 240 ++++++++++++++--------------- src/graphql-root-type-generator.ts | 100 ++++++++---- 2 files changed, 182 insertions(+), 158 deletions(-) diff --git a/demo/root-type.ts b/demo/root-type.ts index d7f2f0a..a1c9856 100644 --- a/demo/root-type.ts +++ b/demo/root-type.ts @@ -9,132 +9,110 @@ import { GraphQLResolveInfo } from 'graphql' import { StringEnum, NumberEnum, NumberEnum2, TypeUnion9 } from './cases' -export type DeepPromisifyReturnType = { - [P in keyof T]: T[P] extends Array - ? Array> - : T[P] extends ReadonlyArray - ? ReadonlyArray> - : T[P] extends (...args: infer P) => infer R - ? (...args: P) => R | Promise - : DeepPromisifyReturnType -} - -export type DeepReturnType = { - [P in keyof T]: T[P] extends Array - ? Array> - : T[P] extends ReadonlyArray - ? ReadonlyArray> - : T[P] extends (...args: any[]) => infer R - ? R extends Promise - ? U - : R - : DeepReturnType +export interface Root { + create(input: { input: CreateInput }, context: TContext, info: GraphQLResolveInfo): MutationResult | Promise + user(input: { id: string }, context: TContext, info: GraphQLResolveInfo): GetResult | Promise> + users(input: {}, context: TContext, info: GraphQLResolveInfo): GetResult | Promise> } -export interface Root { - create(input: { input: CreateInput }, context: TContext, info: GraphQLResolveInfo): DeepPromisifyReturnType> | Promise>> - user(input: { id: string }, context: TContext, info: GraphQLResolveInfo): DeepPromisifyReturnType> | Promise>> - users(input: {}, context: TContext, info: GraphQLResolveInfo): DeepPromisifyReturnType> | Promise>> -} - -export interface TypeLiteral { +export interface TypeLiteral { typeLiteralMember1: number typeLiteralMember2: string } -export interface Interface { +export interface Interface { interfaceMember1?: number interfaceMember2?: string } -export interface TypeUnion1 { +export interface TypeUnion1 { typeLiteralMember1?: number typeLiteralMember2?: string typeUnionMember1?: number typeUnionMember2?: string } -export interface TypeUnion2 { +export interface TypeUnion2 { kind: StringEnum typeUnionMember1?: string typeUnionMember2?: string } -export interface TypeUnion3 { +export interface TypeUnion3 { kind: NumberEnum typeUnionMember1?: string typeUnionMember2?: string } -export interface TypeUnion4 { +export interface TypeUnion4 { kind: string typeUnionMember1?: string typeUnionMember2?: string } -export type TypeUnion5 = TypeLiteral | Interface +export type TypeUnion5 = TypeLiteral | Interface -export type TypeUnion8 = string | string | null | boolean +export type TypeUnion8 = string | string | null | boolean -export interface TypeUnion { - typeUnionMember1: TypeUnion1 - typeUnionMember2: TypeUnion2 - typeUnionMember3: TypeUnion3 - typeUnionMember4: TypeUnion4 - typeUnionMember5: TypeUnion5 +export interface TypeUnion { + typeUnionMember1: TypeUnion1 + typeUnionMember2: TypeUnion2 + typeUnionMember3: TypeUnion3 + typeUnionMember4: TypeUnion4 + typeUnionMember5: TypeUnion5 typeUnionMember6: string | null | boolean typeUnionMember7: string - typeUnionMember8: TypeUnion8 + typeUnionMember8: TypeUnion8 typeUnionMember9: TypeUnion9 } -export interface InterfaceExtends { +export interface InterfaceExtends { interfaceExtendsMember1: number interfaceExtendsMember2: string interfaceMember1?: number interfaceMember2?: string } -export interface TypeIntersection1 { +export interface TypeIntersection1 { interfaceMember1?: number interfaceMember2?: string typeIntersectionMember1: number typeIntersectionMember2: string } -export interface TypeIntersection2 { +export interface TypeIntersection2 { typeIntersectionMember1: number typeIntersectionMember2: string typeIntersectionMember3: number typeIntersectionMember4: string } -export interface TypeIntersection { - typeIntersectionMember1: TypeIntersection1 - typeIntersectionMember2: TypeIntersection2 +export interface TypeIntersection { + typeIntersectionMember1: TypeIntersection1 + typeIntersectionMember2: TypeIntersection2 typeIntersectionMember3: any } -export interface TypeUnionAndIntersection { +export interface TypeUnionAndIntersection { typeIntersectionMember1: number kind: NumberEnum typeUnionMember1?: string typeUnionMember2?: string } -export interface TaggedField { +export interface TaggedField { taggedFieldMember1: number taggedFieldMember2: string } -export interface Enum { +export interface Enum { stringEnum: StringEnum numberEnum: NumberEnum numberEnum2: NumberEnum2 stringEnum2: string } -export interface NumberType { +export interface NumberType { numberMember: number integerMember: number uint32Member: number @@ -152,13 +130,13 @@ export interface NumberType { titleMember: number } -export interface StringType { +export interface StringType { stringMember: string } -export interface ArrayType { +export interface ArrayType { arrayType1: Array - arrayType2: Array> + arrayType2: Array arrayType3: Array arrayType4: Array arrayType5: Array @@ -169,31 +147,31 @@ export interface ArrayType { arrayType10: Array } -export interface MapType7 { +export interface MapType7 { foo: string } -export interface MapType8 { +export interface MapType8 { } -export interface MapType { +export interface MapType { mapType: { [name: string]: number } - mapType2: { [name: string]: TypeLiteral } + mapType2: { [name: string]: TypeLiteral } mapType3: { [name: string]: object } mapType4: { [name: string]: number } mapType5: { [name: string]: any } mapType6: object - mapType7: MapType7 - mapType8: MapType8 + mapType7: MapType7 + mapType8: MapType8 } -export interface Parameter { +export interface Parameter { member1(input: { name: string, age: number }, context: TContext, info: GraphQLResolveInfo): string | Promise member2(input: { name?: string }, context: TContext, info: GraphQLResolveInfo): string | Promise } -export interface DefaultValue { +export interface DefaultValue { stringMember: string numberMember: number booleanMember: boolean @@ -202,29 +180,29 @@ export interface DefaultValue { arrayMember: Array objectMember: object numberMember1: number - objectMember2: TypeLiteral + objectMember2: TypeLiteral } -export type TypeReferenceMember2 = TypeLiteral +export type TypeReferenceMember2 = TypeLiteral -export interface ReferenceType { - typeReferenceMember1: TypeLiteral - typeReferenceMember2: TypeReferenceMember2 +export interface ReferenceType { + typeReferenceMember1: TypeLiteral + typeReferenceMember2: TypeReferenceMember2 } -export interface ClassType1 { +export interface ClassType1 { classMember1: string classMember2: number } -export interface ClassType2 { +export interface ClassType2 { classMember3: string classMember4: number classMember1: string classMember2: number } -export interface ClassType3 { +export interface ClassType3 { classMember1: string classMember2: number classMember3: boolean @@ -233,143 +211,155 @@ export interface ClassType3 { classMember6: object } -export interface ClassType { - classType1: ClassType1 - classType2: ClassType2 - classType3: ClassType3 +export interface ClassType { + classType1: ClassType1 + classType2: ClassType2 + classType3: ClassType3 } -export interface Circular { - children: Array> +export interface Circular { + children: Array } -export interface TypeAlias { - result: Result2 +export interface TypeAlias { + result: Result2 } -export interface CreateInput { +export interface CreateInput { member1: string member2: number - member3: CreateInputMember3 + member3: CreateInputMember3 } -export interface EntryType { +export interface EntryType { optionalMember?: string booleanMember: boolean stringMember: string - numberType: NumberType - arrayType: ArrayType + numberType: NumberType + arrayType: ArrayType typeLiteral: object - referenceType: ReferenceType - interfaceType: Interface - typeUnion: TypeUnion - interfaceExtends: InterfaceExtends - typeIntersection: TypeIntersection - typeUnionAndIntersection: TypeUnionAndIntersection - mapType: MapType - taggedField: TaggedField - enum: Enum - stringNumber: StringType - id: ID + referenceType: ReferenceType + interfaceType: Interface + typeUnion: TypeUnion + interfaceExtends: InterfaceExtends + typeIntersection: TypeIntersection + typeUnionAndIntersection: TypeUnionAndIntersection + mapType: MapType + taggedField: TaggedField + enum: Enum + stringNumber: StringType + id: ID parameter: Parameter optionalArrayMember?: Array tupleType: Array - defaultType: DefaultValue + defaultType: DefaultValue anyType: any - classType: ClassType - circular: Circular - outerType: OuterType - typeAlias: TypeAlias + classType: ClassType + circular: Circular + outerType: OuterType + typeAlias: TypeAlias pick: object pick2: object - pick3: CreateInput2 - unknown: LayoutMetadataMap + pick3: CreateInput2 + unknown: LayoutMetadataMap } -export interface MutationResult { +export interface MutationResult { result: boolean } -export interface GetResult { +export interface GetResult { result: Result } -export interface Result { +export interface Result { member1: string member2(input: { input: string }, context: TContext, info: GraphQLResolveInfo): string | Promise } -export interface CreateInputMember3 { +export interface CreateInputMember3 { member1: string } -export type Result2 = Result3 +export type Result2 = Result3 -export interface Result3 { +export interface Result3 { result3: string } -export interface Pet { +export interface Pet { id?: number name: string photoUrls: Array status: string } -export interface MongooseScheme { - objectId: ObjectId - date: Date - decimal128: Decimal128 +export interface MongooseScheme { + objectId: ObjectId + date: Date + decimal128: Decimal128 index1: string index2: string index3: string - buffer: Buffer + buffer: Buffer } -export interface CreateInput2 { +export interface CreateInput2 { member1: string member2: number } -export interface LayoutMetadataMap { +export interface LayoutMetadataMap { } -export interface Metadata { +export interface Metadata { } -export type WsCommand = CreateBlog | UpdateBlog +export type WsCommand = CreateBlog | UpdateBlog -export interface CreateBlog { +export interface CreateBlog { type: string content: string } -export interface UpdateBlog { +export interface UpdateBlog { type: string id: number content: string } -export type WsPush = BlogChange +export type WsPush = BlogChange -export interface BlogChange { +export interface BlogChange { type: string id: number content: string } -export interface TestController { +export interface TestController { get(input: { foo: number, bar: string }, context: TContext, info: GraphQLResolveInfo): any | Promise } -export interface OuterType { +export interface OuterType { outerType: number } -export interface ResolveResult { - create: DeepReturnType> +export type DeepReturnType = { + [P in keyof T]: T[P] extends Array + ? Array> + : T[P] extends ReadonlyArray + ? ReadonlyArray> + : T[P] extends (...args: any[]) => infer R + ? R extends Promise + ? U + : R + : DeepReturnType +} + +export interface ResolveResult { + create: DeepReturnType user: DeepReturnType> users: DeepReturnType> } diff --git a/src/graphql-root-type-generator.ts b/src/graphql-root-type-generator.ts index a200635..8f29ba7 100644 --- a/src/graphql-root-type-generator.ts +++ b/src/graphql-root-type-generator.ts @@ -1,41 +1,82 @@ -import { TypeDeclaration, Type, Parameter, EnumType, StringDeclaration, EnumDeclaration } from './utils' +import { TypeDeclaration, Type, Parameter, EnumType, StringDeclaration, EnumDeclaration, getReferencesInType } from './utils' export function generateGraphqlRootType(declarations: TypeDeclaration[], getReferenceTypeImports: (referenceTypes: ReferenceType[]) => string) { const rootTypes: string[] = [] const nonRootTypes: string[] = [] const resolveResults: string[] = [] const referenceTypes: ReferenceType[] = [] + + let remainDeclarations = [...declarations] + const declarationsWithParameters: string[] = [] + let tmp: TypeDeclaration[] = [] + for (; ;) { + for (const d of remainDeclarations) { + if (d.kind === 'object') { + if (d.members.some((m) => m.parameters)) { + declarationsWithParameters.push(d.name) + } else if (d.members.some((m) => getReferencesInType(m.type).some((r) => declarationsWithParameters.includes(r.name)))) { + declarationsWithParameters.push(d.name) + } else { + tmp.push(d) + } + } else if (d.kind === 'reference') { + if (declarationsWithParameters.includes(d.name)) { + declarationsWithParameters.push(d.newName) + } else { + tmp.push(d) + } + } else if (d.kind === 'union') { + if (d.members.some((m) => getReferencesInType(m).some((r) => declarationsWithParameters.includes(r.name)))) { + declarationsWithParameters.push(d.name) + } else { + tmp.push(d) + } + } else { + tmp.push(d) + } + } + if (tmp.length == remainDeclarations.length) { + break + } + remainDeclarations = tmp + tmp = [] + } + console.info(declarationsWithParameters) + for (const typeDeclaration of declarations) { if (typeDeclaration.kind === 'object') { const isQueryOrMutation = typeDeclaration.name === 'Query' || typeDeclaration.name === 'Mutation' if (isQueryOrMutation) { for (const member of typeDeclaration.members) { - const memberType = getMemberType(member.type, referenceTypes, declarations) - const parameters = getMemberParameters(referenceTypes, declarations, member.parameters) - rootTypes.push(` ${member.name}(${parameters}, context: TContext, info: GraphQLResolveInfo): DeepPromisifyReturnType<${memberType}> | Promise>`) + const memberType = getMemberType(member.type, referenceTypes, declarations, declarationsWithParameters) + const parameters = getMemberParameters(referenceTypes, declarations, declarationsWithParameters, member.parameters) + rootTypes.push(` ${member.name}(${parameters}, context: TContext, info: GraphQLResolveInfo): ${memberType} | Promise<${memberType}>`) resolveResults.push(` ${member.name}: DeepReturnType<${memberType}>`) } } else { const nonRootTypeMembers: string[] = [] for (const member of typeDeclaration.members) { - const memberType = getMemberType(member.type, referenceTypes, declarations) + const memberType = getMemberType(member.type, referenceTypes, declarations, declarationsWithParameters) const optionalToken = member.optional ? '?' : '' if (member.parameters) { - const parameters = getMemberParameters(referenceTypes, declarations, member.parameters) + const parameters = getMemberParameters(referenceTypes, declarations, declarationsWithParameters, member.parameters) nonRootTypeMembers.push(` ${member.name}${optionalToken}(${parameters}, context: TContext, info: GraphQLResolveInfo): ${memberType} | Promise<${memberType}>`) } else { nonRootTypeMembers.push(` ${member.name}${optionalToken}: ${memberType}`) } } - nonRootTypes.push(`export interface ${typeDeclaration.name} { + const generic = declarationsWithParameters.includes(typeDeclaration.name) ? '' : '' + nonRootTypes.push(`export interface ${typeDeclaration.name}${generic} { ${nonRootTypeMembers.join('\n')} }`) } } else if (typeDeclaration.kind === 'reference') { - nonRootTypes.push(`export type ${typeDeclaration.newName} = ${typeDeclaration.name}`) + const generic = declarationsWithParameters.includes(typeDeclaration.name) ? '' : '' + nonRootTypes.push(`export type ${typeDeclaration.newName}${generic} = ${typeDeclaration.name}${generic}`) } else if (typeDeclaration.kind === 'union') { - const memberType = getMemberType(typeDeclaration, referenceTypes, declarations) - nonRootTypes.push(`export type ${typeDeclaration.name} = ${memberType}`) + const generic = declarationsWithParameters.includes(typeDeclaration.name) ? '' : '' + const memberType = getMemberType(typeDeclaration, referenceTypes, declarations, declarationsWithParameters) + nonRootTypes.push(`export type ${typeDeclaration.name}${generic} = ${memberType}`) } else if (typeDeclaration.kind === 'string') { if (typeDeclaration.enums && !isNativeType(typeDeclaration.name)) { referenceTypes.push(typeDeclaration) @@ -56,16 +97,12 @@ import { GraphQLResolveInfo } from 'graphql' ${referenceTypeImports} -export type DeepPromisifyReturnType = { - [P in keyof T]: T[P] extends Array - ? Array> - : T[P] extends ReadonlyArray - ? ReadonlyArray> - : T[P] extends (...args: infer P) => infer R - ? (...args: P) => R | Promise - : DeepPromisifyReturnType +export interface Root { +${rootTypes.join('\n')} } +${nonRootTypes.join('\n\n')} + export type DeepReturnType = { [P in keyof T]: T[P] extends Array ? Array> @@ -78,13 +115,7 @@ export type DeepReturnType = { : DeepReturnType } -export interface Root { -${rootTypes.join('\n')} -} - -${nonRootTypes.join('\n\n')} - -export interface ResolveResult { +export interface ResolveResult { ${resolveResults.join('\n')} } @@ -95,11 +126,11 @@ ${resolveResults.join('\n')} export type ReferenceType = EnumType | StringDeclaration | EnumDeclaration -function getMemberParameters(referenceTypes: ReferenceType[], declarations: TypeDeclaration[], parameters?: Parameter[]) { +function getMemberParameters(referenceTypes: ReferenceType[], declarations: TypeDeclaration[], declarationsWithParameters: string[], parameters?: Parameter[]) { if (parameters && parameters.length > 0) { const parameterString = parameters.map((parameter) => { const optionalToken = parameter.optional ? '?' : '' - const parameterType = getMemberType(parameter.type, referenceTypes, declarations) + const parameterType = getMemberType(parameter.type, referenceTypes, declarations, declarationsWithParameters) return `${parameter.name}${optionalToken}: ${parameterType}` }).join(', ') return `input: { ${parameterString} }` @@ -111,9 +142,9 @@ function isNativeType(typeName: string) { return typeName === 'string' || typeName === 'number' || typeName === 'boolean' } -function getMemberType(memberType: Type, referenceTypes: ReferenceType[], declarations: TypeDeclaration[]): string { +function getMemberType(memberType: Type, referenceTypes: ReferenceType[], declarations: TypeDeclaration[], declarationsWithParameters: string[]): string { if (memberType.kind === 'array') { - return `Array<${getMemberType(memberType.type, referenceTypes, declarations)}>` + return `Array<${getMemberType(memberType.type, referenceTypes, declarations, declarationsWithParameters)}>` } if (memberType.kind === 'enum') { if (!isNativeType(memberType.name)) { @@ -126,15 +157,18 @@ function getMemberType(memberType: Type, referenceTypes: ReferenceType[], declar if (declaration && (declaration.kind === 'enum' || (declaration.kind === 'string' && declaration.enums))) { return memberType.name } - return memberType.name + '' + if (declarationsWithParameters.includes(memberType.name)) { + return memberType.name + '' + } + return memberType.name } if (memberType.kind === 'map') { - const mapKeyType = getMemberType(memberType.key, referenceTypes, declarations) - const mapValueType = getMemberType(memberType.value, referenceTypes, declarations) + const mapKeyType = getMemberType(memberType.key, referenceTypes, declarations, declarationsWithParameters) + const mapValueType = getMemberType(memberType.value, referenceTypes, declarations, declarationsWithParameters) return `{ [name: ${mapKeyType}]: ${mapValueType} }` } if (memberType.kind === 'union') { - return memberType.members.map((member) => getMemberType(member, referenceTypes, declarations)).join(' | ') + return memberType.members.map((member) => getMemberType(member, referenceTypes, declarations, declarationsWithParameters)).join(' | ') } if (memberType.kind === undefined) { return 'any'