From 39660f14232e8245f554e53911efa6fe067939b0 Mon Sep 17 00:00:00 2001 From: Dan Adajian Date: Mon, 22 Apr 2024 10:09:05 -0500 Subject: [PATCH 1/6] feat: support marker interfaces for union types --- .eslintrc.json | 1 + src/config.ts | 1 + src/definitions/object.ts | 11 ++++++-- src/definitions/union.ts | 14 +++++++++-- src/helpers/add-dependent-types.ts | 2 +- src/helpers/build-annotations.ts | 4 +-- src/helpers/build-type-metadata.ts | 1 + src/helpers/dependent-type-utils.ts | 25 ++++++++++++++++++- src/helpers/get-dependent-type-names.ts | 13 +++++----- .../codegen.config.ts | 5 ++++ .../should_honor_union_config/expected.kt | 21 ++++++++++++++++ .../should_honor_union_config/schema.graphql | 21 ++++++++++++++++ 12 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 test/unit/should_honor_union_config/codegen.config.ts create mode 100644 test/unit/should_honor_union_config/expected.kt create mode 100644 test/unit/should_honor_union_config/schema.graphql diff --git a/.eslintrc.json b/.eslintrc.json index 75bd797..6c8b963 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,6 +6,7 @@ }, "plugins": ["@typescript-eslint"], "rules": { + "no-console": "error", "@typescript-eslint/no-non-null-assertion": "error", "@typescript-eslint/no-unsafe-argument": "error", "@typescript-eslint/no-unsafe-call": "error" diff --git a/src/config.ts b/src/config.ts index 5a3a217..6a0fab9 100644 --- a/src/config.ts +++ b/src/config.ts @@ -129,4 +129,5 @@ export const configSchema = object({ }), ), ), + useMarkerInterfaces: optional(boolean()), }); diff --git a/src/definitions/object.ts b/src/definitions/object.ts index 56885d1..f716838 100644 --- a/src/definitions/object.ts +++ b/src/definitions/object.ts @@ -16,7 +16,10 @@ import { buildAnnotations } from "../helpers/build-annotations"; import { indent } from "@graphql-codegen/visitor-plugin-common"; import { buildTypeMetadata } from "../helpers/build-type-metadata"; import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-definition"; -import { getDependentInterfaceNames } from "../helpers/dependent-type-utils"; +import { + getDependentInterfaceNames, + getDependentUnionsForType, +} from "../helpers/dependent-type-utils"; import { isResolverType } from "../helpers/is-resolver-type"; import { buildFieldDefinition } from "../helpers/build-field-definition"; import { isExternalField } from "../helpers/is-external-field"; @@ -36,7 +39,11 @@ export function buildObjectTypeDefinition( definitionNode: node, }); const name = node.name.value; - const interfacesToInherit = getDependentInterfaceNames(node); + const dependentInterfaces = getDependentInterfaceNames(node); + const dependentUnions = getDependentUnionsForType(schema, node); + const interfacesToInherit = config.useMarkerInterfaces + ? dependentInterfaces.concat(dependentUnions) + : dependentInterfaces; const interfaceInheritance = `${interfacesToInherit.length ? ` : ${interfacesToInherit.join(", ")}` : ""}`; if (isResolverType(node, config)) { diff --git a/src/definitions/union.ts b/src/definitions/union.ts index e0a98f1..b8ce8d7 100644 --- a/src/definitions/union.ts +++ b/src/definitions/union.ts @@ -15,7 +15,10 @@ import { UnionTypeDefinitionNode } from "graphql"; import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-definition"; import { buildDirectiveAnnotations } from "../helpers/build-directive-annotations"; import { CodegenConfig } from "../plugin"; -import { trimDescription } from "../helpers/build-annotations"; +import { + buildAnnotations, + trimDescription, +} from "../helpers/build-annotations"; export function buildUnionTypeDefinition( node: UnionTypeDefinitionNode, @@ -24,6 +27,13 @@ export function buildUnionTypeDefinition( if (!shouldIncludeTypeDefinition(node, config)) { return ""; } + const annotations = buildAnnotations({ + config, + definitionNode: node, + }); + if (config.useMarkerInterfaces) { + return `${annotations}interface ${node.name.value}`; + } const directiveAnnotations = buildDirectiveAnnotations(node, config); const possibleTypes = @@ -31,7 +41,7 @@ export function buildUnionTypeDefinition( return `${directiveAnnotations}@GraphQLUnion( name = "${node.name.value}", possibleTypes = [${possibleTypes}], - description = "${node.description?.value ? trimDescription(node.description.value) : ""}" + description = "${trimDescription(node.description?.value)}" ) annotation class ${node.name.value}`; } diff --git a/src/helpers/add-dependent-types.ts b/src/helpers/add-dependent-types.ts index a5097e4..4b813bd 100644 --- a/src/helpers/add-dependent-types.ts +++ b/src/helpers/add-dependent-types.ts @@ -25,7 +25,7 @@ export function addDependentTypes( .map((typeName) => schema.getType(typeName)?.astNode) .filter(Boolean); const dependentTypeNames = onlyTypesNodes.flatMap((node) => - getDependentTypeNames(schema, node, config.dependentTypesInScope), + getDependentTypeNames(schema, node, config), ); const dependentTypesInScope = dependentTypeNames.filter((typeName) => dependentTypeIsInScope(typeName, config), diff --git a/src/helpers/build-annotations.ts b/src/helpers/build-annotations.ts index 0a0d12b..61eea79 100644 --- a/src/helpers/build-annotations.ts +++ b/src/helpers/build-annotations.ts @@ -95,10 +95,10 @@ export function isDeprecatedDescription( ); } -export function trimDescription(description: string) { +export function trimDescription(description?: string) { return ( description - .split("\n") + ?.split("\n") .map((str) => str.trim().replaceAll('"', "").replaceAll("\\", "")) .find((str) => str.match(/^[a-zA-Z]/)) ?? "" ); diff --git a/src/helpers/build-type-metadata.ts b/src/helpers/build-type-metadata.ts index 2682774..430bed3 100644 --- a/src/helpers/build-type-metadata.ts +++ b/src/helpers/build-type-metadata.ts @@ -60,6 +60,7 @@ export function buildTypeMetadata( }; } else if (isUnionType(schemaType)) { const shouldTreatUnionAsInterface = + config.useMarkerInterfaces || config.externalUnionsAsInterfaces?.includes(schemaType.name); return { ...commonMetadata, diff --git a/src/helpers/dependent-type-utils.ts b/src/helpers/dependent-type-utils.ts index d8d6e65..51a4bc2 100644 --- a/src/helpers/dependent-type-utils.ts +++ b/src/helpers/dependent-type-utils.ts @@ -11,7 +11,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Kind, TypeDefinitionNode, TypeNode } from "graphql"; +import { + GraphQLSchema, + GraphQLUnionType, + Kind, + TypeDefinitionNode, + TypeNode, +} from "graphql"; import { CodegenConfig } from "../plugin"; export function getDependentFieldTypeNames( @@ -55,3 +61,20 @@ export function getDependentUnionNames(node: TypeDefinitionNode) { ? node.types?.map((type) => type.name.value) ?? [] : []; } + +export function getDependentUnionsForType( + schema: GraphQLSchema, + node: TypeDefinitionNode, +) { + const typeMap = schema.getTypeMap(); + const unions = Object.keys(typeMap) + .filter( + (type) => typeMap[type]?.astNode?.kind === Kind.UNION_TYPE_DEFINITION, + ) + .map((type) => typeMap[type] as GraphQLUnionType); + return unions + .filter((union) => + union.getTypes().some((type) => type.name === node.name.value), + ) + .map((union) => union.name); +} diff --git a/src/helpers/get-dependent-type-names.ts b/src/helpers/get-dependent-type-names.ts index f5c26a8..08db1fe 100644 --- a/src/helpers/get-dependent-type-names.ts +++ b/src/helpers/get-dependent-type-names.ts @@ -18,21 +18,22 @@ import { getDependentInterfaceNames, getDependentUnionNames, } from "./dependent-type-utils"; -import { CodegenConfig } from "../plugin"; +import { GraphQLKotlinCodegenConfig } from "../plugin"; export function getDependentTypeNames( schema: GraphQLSchema, node: TypeDefinitionNode, - dependentTypesInScope: CodegenConfig["dependentTypesInScope"], + config: GraphQLKotlinCodegenConfig, ): string[] { - const namedTypes = getDependentFieldTypeNames(node, dependentTypesInScope) + const namedTypes = getDependentFieldTypeNames( + node, + config.dependentTypesInScope, + ) .concat(getDependentUnionNames(node)) .concat(getDependentInterfaceNames(node)); const recursivelyFoundTypes = namedTypes .map((typeName) => schema.getType(typeName)?.astNode) .filter(Boolean) - .flatMap((node) => - getDependentTypeNames(schema, node, dependentTypesInScope), - ); + .flatMap((node) => getDependentTypeNames(schema, node, config)); return namedTypes.concat(recursivelyFoundTypes); } diff --git a/test/unit/should_honor_union_config/codegen.config.ts b/test/unit/should_honor_union_config/codegen.config.ts new file mode 100644 index 0000000..8c9aae5 --- /dev/null +++ b/test/unit/should_honor_union_config/codegen.config.ts @@ -0,0 +1,5 @@ +import { GraphQLKotlinCodegenConfig } from "../../../src/plugin"; + +export default { + useMarkerInterfaces: true, +} satisfies GraphQLKotlinCodegenConfig; diff --git a/test/unit/should_honor_union_config/expected.kt b/test/unit/should_honor_union_config/expected.kt new file mode 100644 index 0000000..47ec0f6 --- /dev/null +++ b/test/unit/should_honor_union_config/expected.kt @@ -0,0 +1,21 @@ +package com.kotlin.generated + +import com.expediagroup.graphql.generator.annotations.* + +@GraphQLDescription("A description for TypeForHonoringUnionConfig1") +data class TypeForHonoringUnionConfig1( + val field: String? = null +) : UnionAsMarkerInterface + +data class TypeForHonoringUnionConfig2( + val field: String? = null +) : UnionAsMarkerInterface + +@GraphQLDescription("A description for UnionAsMarkerInterface") +interface UnionAsMarkerInterface + +data class UnionForHonoringUnionConfig( + @GraphQLDescription("A description for field") + val field: UnionAsMarkerInterface? = null, + val field2: String? = null +) diff --git a/test/unit/should_honor_union_config/schema.graphql b/test/unit/should_honor_union_config/schema.graphql new file mode 100644 index 0000000..9a3f154 --- /dev/null +++ b/test/unit/should_honor_union_config/schema.graphql @@ -0,0 +1,21 @@ +"A description for TypeForHonoringUnionConfig1" +type TypeForHonoringUnionConfig1 { + field: String +} + +type TypeForHonoringUnionConfig2 { + field: String +} + +""" +A description for UnionAsMarkerInterface +""" +union UnionAsMarkerInterface = + | TypeForHonoringUnionConfig1 + | TypeForHonoringUnionConfig2 + +type UnionForHonoringUnionConfig { + "A description for field" + field: UnionAsMarkerInterface + field2: String +} From 25690ba9b1078ebcc7e81576da48af3db11ac788 Mon Sep 17 00:00:00 2001 From: Dan Adajian Date: Mon, 22 Apr 2024 12:00:55 -0500 Subject: [PATCH 2/6] set marker interfaces as the default --- src/config.ts | 5 +++- src/definitions/object.ts | 7 +++-- src/definitions/union.ts | 16 +++++------ src/helpers/build-annotations.ts | 8 ++---- src/helpers/build-type-metadata.ts | 2 +- .../expected.kt | 23 ++++++--------- .../schema.graphql | 2 +- .../expected.kt | 24 ++++------------ .../expected.kt | 18 ++++-------- .../expected.kt | 18 ++++-------- .../expected.kt | 15 ++++------ .../expected.kt | 15 +++------- .../externalClasses.kt | 2 +- .../expected.kt | 10 ++----- .../should_honor_union_config/expected.kt | 21 -------------- .../should_honor_union_config/schema.graphql | 21 -------------- .../codegen.config.ts | 2 +- .../expected.kt | 28 +++++++++++++++++++ .../schema.graphql | 22 +++++++++++++++ .../expected.kt | 25 +++++------------ 20 files changed, 118 insertions(+), 166 deletions(-) delete mode 100644 test/unit/should_honor_union_config/expected.kt delete mode 100644 test/unit/should_honor_union_config/schema.graphql rename test/unit/{should_honor_union_config => should_honor_union_generation_config}/codegen.config.ts (76%) create mode 100644 test/unit/should_honor_union_generation_config/expected.kt create mode 100644 test/unit/should_honor_union_generation_config/schema.graphql diff --git a/src/config.ts b/src/config.ts index 6a0fab9..abe1033 100644 --- a/src/config.ts +++ b/src/config.ts @@ -15,6 +15,7 @@ import { array, boolean, enum_, + literal, object, optional, string, @@ -129,5 +130,7 @@ export const configSchema = object({ }), ), ), - useMarkerInterfaces: optional(boolean()), + unionGeneration: optional( + union([literal("ANNOTATION_CLASS"), literal("MARKER_INTERFACE")]), + ), }); diff --git a/src/definitions/object.ts b/src/definitions/object.ts index f716838..ad4c2aa 100644 --- a/src/definitions/object.ts +++ b/src/definitions/object.ts @@ -41,9 +41,10 @@ export function buildObjectTypeDefinition( const name = node.name.value; const dependentInterfaces = getDependentInterfaceNames(node); const dependentUnions = getDependentUnionsForType(schema, node); - const interfacesToInherit = config.useMarkerInterfaces - ? dependentInterfaces.concat(dependentUnions) - : dependentInterfaces; + const interfacesToInherit = + config.unionGeneration === "ANNOTATION_CLASS" + ? dependentInterfaces + : dependentInterfaces.concat(dependentUnions); const interfaceInheritance = `${interfacesToInherit.length ? ` : ${interfacesToInherit.join(", ")}` : ""}`; if (isResolverType(node, config)) { diff --git a/src/definitions/union.ts b/src/definitions/union.ts index b8ce8d7..6f82f83 100644 --- a/src/definitions/union.ts +++ b/src/definitions/union.ts @@ -31,17 +31,17 @@ export function buildUnionTypeDefinition( config, definitionNode: node, }); - if (config.useMarkerInterfaces) { - return `${annotations}interface ${node.name.value}`; - } - - const directiveAnnotations = buildDirectiveAnnotations(node, config); - const possibleTypes = - node.types?.map((type) => `${type.name.value}::class`).join(", ") || ""; - return `${directiveAnnotations}@GraphQLUnion( + if (config.unionGeneration === "ANNOTATION_CLASS") { + const directiveAnnotations = buildDirectiveAnnotations(node, config); + const possibleTypes = + node.types?.map((type) => `${type.name.value}::class`).join(", ") || ""; + return `${directiveAnnotations}@GraphQLUnion( name = "${node.name.value}", possibleTypes = [${possibleTypes}], description = "${trimDescription(node.description?.value)}" ) annotation class ${node.name.value}`; + } + + return `${annotations}interface ${node.name.value}`; } diff --git a/src/helpers/build-annotations.ts b/src/helpers/build-annotations.ts index 61eea79..bcea9dd 100644 --- a/src/helpers/build-annotations.ts +++ b/src/helpers/build-annotations.ts @@ -41,13 +41,11 @@ export function buildAnnotations({ }) { const description = inputDescription ?? definitionNode?.description?.value ?? ""; - const descriptionAnnotator = isDeprecatedDescription( - description, - resolvedType, - ) + const isDeprecated = isDeprecatedDescription(description, resolvedType); + const descriptionAnnotator = isDeprecated ? "@Deprecated" : "@GraphQLDescription"; - const descriptionValue = isDeprecatedDescription(description, resolvedType) + const descriptionValue = isDeprecated ? description.replace("DEPRECATED: ", "") : description; const trimmedDescription = trimDescription(descriptionValue); diff --git a/src/helpers/build-type-metadata.ts b/src/helpers/build-type-metadata.ts index 430bed3..e45cd6d 100644 --- a/src/helpers/build-type-metadata.ts +++ b/src/helpers/build-type-metadata.ts @@ -60,7 +60,7 @@ export function buildTypeMetadata( }; } else if (isUnionType(schemaType)) { const shouldTreatUnionAsInterface = - config.useMarkerInterfaces || + config.unionGeneration !== "ANNOTATION_CLASS" || config.externalUnionsAsInterfaces?.includes(schemaType.name); return { ...commonMetadata, diff --git a/test/unit/should_annotate_types_properly/expected.kt b/test/unit/should_annotate_types_properly/expected.kt index 93ef4cf..1ad5a55 100644 --- a/test/unit/should_annotate_types_properly/expected.kt +++ b/test/unit/should_annotate_types_properly/expected.kt @@ -17,22 +17,15 @@ data class TypeThatShouldBeProperlyAnnotated( val deprecated3: String? = null, @Deprecated("It only takes the first one") val deprecated4: String? = null, - @UnionThatShouldBeProperlyAnnotated - @GraphQLDescription("DEPRECATED: It uses the GraphQLDescription annotation for union types") - val deprecated5: Any? = null, - @UnionThatShouldBeProperlyAnnotated - @GraphQLDescription("It uses the GraphQLDescription annotation for union types") - val deprecated6: Any? = null, - @UnionThatShouldBeProperlyAnnotated + @Deprecated("It uses the GraphQLDescription annotation for union types") + val deprecated5: UnionThatShouldBeProperlyAnnotated? = null, + @Deprecated("It uses the GraphQLDescription annotation for union types") + val deprecated6: UnionThatShouldBeProperlyAnnotated? = null, @GraphQLDescription("When there is a description") - val deprecated7: Any? = null, + @Deprecated("It uses the @Deprecated annotation for the reason") + val deprecated7: UnionThatShouldBeProperlyAnnotated? = null, @Deprecated("Multiline reason") val deprecated8: String? = null -) +) : UnionThatShouldBeProperlyAnnotated -@GraphQLUnion( - name = "UnionThatShouldBeProperlyAnnotated", - possibleTypes = [TypeThatShouldBeProperlyAnnotated::class], - description = "" -) -annotation class UnionThatShouldBeProperlyAnnotated +interface UnionThatShouldBeProperlyAnnotated diff --git a/test/unit/should_annotate_types_properly/schema.graphql b/test/unit/should_annotate_types_properly/schema.graphql index d039336..ffdde85 100644 --- a/test/unit/should_annotate_types_properly/schema.graphql +++ b/test/unit/should_annotate_types_properly/schema.graphql @@ -26,7 +26,7 @@ type TypeThatShouldBeProperlyAnnotated { ) "When there is a description" deprecated7: UnionThatShouldBeProperlyAnnotated - @deprecated(reason: "It omits the @Deprecated annotation for now") + @deprecated(reason: "It uses the @Deprecated annotation for the reason") deprecated8: String @deprecated( reason: "\n Multiline reason\n with spaces\n " diff --git a/test/unit/should_generate_multi-union_types_properly/expected.kt b/test/unit/should_generate_multi-union_types_properly/expected.kt index a84a223..f5bd4a8 100644 --- a/test/unit/should_generate_multi-union_types_properly/expected.kt +++ b/test/unit/should_generate_multi-union_types_properly/expected.kt @@ -4,29 +4,17 @@ import com.expediagroup.graphql.generator.annotations.* data class MyType3( val field: String? = null -) +) : MyUnion1, MyUnion2 data class MyType4( val field: String? = null -) +) : MyUnion1, MyUnion2 -@GraphQLUnion( - name = "MyUnion1", - possibleTypes = [MyType3::class, MyType4::class], - description = "" -) -annotation class MyUnion1 +interface MyUnion1 -@GraphQLUnion( - name = "MyUnion2", - possibleTypes = [MyType3::class, MyType4::class], - description = "" -) -annotation class MyUnion2 +interface MyUnion2 data class MyMultiUnionType( - @MyUnion1 - val field: Any? = null, - @MyUnion2 - val field2: Any? = null + val field: MyUnion1? = null, + val field2: MyUnion2? = null ) diff --git a/test/unit/should_generate_non-nullable_union_list_types_properly/expected.kt b/test/unit/should_generate_non-nullable_union_list_types_properly/expected.kt index 3cf852b..aa7142b 100644 --- a/test/unit/should_generate_non-nullable_union_list_types_properly/expected.kt +++ b/test/unit/should_generate_non-nullable_union_list_types_properly/expected.kt @@ -5,22 +5,16 @@ import com.expediagroup.graphql.generator.annotations.* @GraphQLDescription("A description for MyType1") data class TypeForNonNullableUnionList1( val field: String? = null -) +) : UnionForNonNullableList data class TypeForNonNullableUnionList2( val field: String? = null -) +) : UnionForNonNullableList -@GraphQLUnion( - name = "UnionForNonNullableList", - possibleTypes = [TypeForNonNullableUnionList1::class, TypeForNonNullableUnionList2::class], - description = "A description for UnionForNonNullableList" -) -annotation class UnionForNonNullableList +@GraphQLDescription("A description for UnionForNonNullableList") +interface UnionForNonNullableList data class MyNonNullableUnionListType( - @UnionForNonNullableList - val field: List = emptyList(), - @UnionForNonNullableList - val field2: List = emptyList() + val field: List = emptyList(), + val field2: List = emptyList() ) diff --git a/test/unit/should_generate_union_list_types_properly/expected.kt b/test/unit/should_generate_union_list_types_properly/expected.kt index 1777385..d35f292 100644 --- a/test/unit/should_generate_union_list_types_properly/expected.kt +++ b/test/unit/should_generate_union_list_types_properly/expected.kt @@ -5,23 +5,17 @@ import com.expediagroup.graphql.generator.annotations.* @GraphQLDescription("A description for TypeForGeneratingUnionListTypes1") data class TypeForGeneratingUnionListTypes1( val field: String? = null -) +) : UnionForGeneratingUnionListTypes data class TypeForGeneratingUnionListTypes2( val field: String? = null -) +) : UnionForGeneratingUnionListTypes -@GraphQLUnion( - name = "UnionForGeneratingUnionListTypes", - possibleTypes = [TypeForGeneratingUnionListTypes1::class, TypeForGeneratingUnionListTypes2::class], - description = "A description for UnionForGeneratingUnionListTypes" -) -annotation class UnionForGeneratingUnionListTypes +@GraphQLDescription("A description for UnionForGeneratingUnionListTypes") +interface UnionForGeneratingUnionListTypes data class MyUnionListType( - @UnionForGeneratingUnionListTypes @GraphQLDescription("A description for field") - val field: List? = null, - @UnionForGeneratingUnionListTypes - val field2: List? = null + val field: List? = null, + val field2: List? = null ) diff --git a/test/unit/should_generate_union_types_properly/expected.kt b/test/unit/should_generate_union_types_properly/expected.kt index 229f757..a06a063 100644 --- a/test/unit/should_generate_union_types_properly/expected.kt +++ b/test/unit/should_generate_union_types_properly/expected.kt @@ -5,22 +5,17 @@ import com.expediagroup.graphql.generator.annotations.* @GraphQLDescription("A description for MyType1") data class TypeForGeneratingUnionTypesProperly1( val field: String? = null -) +) : UnionForGeneratingUnionsProperly data class TypeForGeneratingUnionTypesProperly2( val field: String? = null -) +) : UnionForGeneratingUnionsProperly -@GraphQLUnion( - name = "UnionForGeneratingUnionsProperly", - possibleTypes = [TypeForGeneratingUnionTypesProperly1::class, TypeForGeneratingUnionTypesProperly2::class], - description = "A trimmed description for UnionForGeneratingUnionsProperly" -) -annotation class UnionForGeneratingUnionsProperly +@GraphQLDescription("A trimmed description for UnionForGeneratingUnionsProperly") +interface UnionForGeneratingUnionsProperly data class MyUnionType( - @UnionForGeneratingUnionsProperly @GraphQLDescription("A description for field") - val field: Any? = null, + val field: UnionForGeneratingUnionsProperly? = null, val field2: String? = null ) diff --git a/test/unit/should_honor_dependentTypesInScope_config/expected.kt b/test/unit/should_honor_dependentTypesInScope_config/expected.kt index 6096c18..e7ea92e 100644 --- a/test/unit/should_honor_dependentTypesInScope_config/expected.kt +++ b/test/unit/should_honor_dependentTypesInScope_config/expected.kt @@ -10,20 +10,13 @@ data class MyTypeInOnlyTypes( data class TypeInScope( val field: String? = null, - @UnionInScope - val unionInScopeField: Any? = null, - @UnionOutOfScope - val unionOutOfScopeField: Any? = null, + val unionInScopeField: UnionInScope? = null, + val unionOutOfScopeField: UnionOutOfScope? = null, val externalUnionAsInterfaceField: ExternalUnionAsInterface? = null ) -@GraphQLUnion( - name = "UnionInScope", - possibleTypes = [Type1::class], - description = "" -) -annotation class UnionInScope +interface UnionInScope data class Type1( val field: String? = null -) +) : UnionInScope, ExternalUnionAsInterface diff --git a/test/unit/should_honor_dependentTypesInScope_config/externalClasses.kt b/test/unit/should_honor_dependentTypesInScope_config/externalClasses.kt index cd9d127..92abfe7 100644 --- a/test/unit/should_honor_dependentTypesInScope_config/externalClasses.kt +++ b/test/unit/should_honor_dependentTypesInScope_config/externalClasses.kt @@ -4,4 +4,4 @@ data class TypeOutOfScope(val value: String) interface ExternalUnionAsInterface -annotation class UnionOutOfScope +interface UnionOutOfScope diff --git a/test/unit/should_honor_directiveReplacements_config/expected.kt b/test/unit/should_honor_directiveReplacements_config/expected.kt index 08dd3dc..b353397 100644 --- a/test/unit/should_honor_directiveReplacements_config/expected.kt +++ b/test/unit/should_honor_directiveReplacements_config/expected.kt @@ -9,13 +9,9 @@ import should_honor_directiveReplacements_config.* @SomeAnnotationWithArgs(arg1 = "arg1", arg2 = 0) data class TypeHonoringDirectiveReplacements( val field: String? = null -) +) : MyDirectiveUnion +@GraphQLDescription("A description for MyDirectiveUnion") @SomeAnnotation1 @SomeAnnotation2 -@GraphQLUnion( - name = "MyDirectiveUnion", - possibleTypes = [TypeHonoringDirectiveReplacements::class], - description = "A description for MyDirectiveUnion" -) -annotation class MyDirectiveUnion +interface MyDirectiveUnion diff --git a/test/unit/should_honor_union_config/expected.kt b/test/unit/should_honor_union_config/expected.kt deleted file mode 100644 index 47ec0f6..0000000 --- a/test/unit/should_honor_union_config/expected.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.kotlin.generated - -import com.expediagroup.graphql.generator.annotations.* - -@GraphQLDescription("A description for TypeForHonoringUnionConfig1") -data class TypeForHonoringUnionConfig1( - val field: String? = null -) : UnionAsMarkerInterface - -data class TypeForHonoringUnionConfig2( - val field: String? = null -) : UnionAsMarkerInterface - -@GraphQLDescription("A description for UnionAsMarkerInterface") -interface UnionAsMarkerInterface - -data class UnionForHonoringUnionConfig( - @GraphQLDescription("A description for field") - val field: UnionAsMarkerInterface? = null, - val field2: String? = null -) diff --git a/test/unit/should_honor_union_config/schema.graphql b/test/unit/should_honor_union_config/schema.graphql deleted file mode 100644 index 9a3f154..0000000 --- a/test/unit/should_honor_union_config/schema.graphql +++ /dev/null @@ -1,21 +0,0 @@ -"A description for TypeForHonoringUnionConfig1" -type TypeForHonoringUnionConfig1 { - field: String -} - -type TypeForHonoringUnionConfig2 { - field: String -} - -""" -A description for UnionAsMarkerInterface -""" -union UnionAsMarkerInterface = - | TypeForHonoringUnionConfig1 - | TypeForHonoringUnionConfig2 - -type UnionForHonoringUnionConfig { - "A description for field" - field: UnionAsMarkerInterface - field2: String -} diff --git a/test/unit/should_honor_union_config/codegen.config.ts b/test/unit/should_honor_union_generation_config/codegen.config.ts similarity index 76% rename from test/unit/should_honor_union_config/codegen.config.ts rename to test/unit/should_honor_union_generation_config/codegen.config.ts index 8c9aae5..8b31606 100644 --- a/test/unit/should_honor_union_config/codegen.config.ts +++ b/test/unit/should_honor_union_generation_config/codegen.config.ts @@ -1,5 +1,5 @@ import { GraphQLKotlinCodegenConfig } from "../../../src/plugin"; export default { - useMarkerInterfaces: true, + unionGeneration: "ANNOTATION_CLASS", } satisfies GraphQLKotlinCodegenConfig; diff --git a/test/unit/should_honor_union_generation_config/expected.kt b/test/unit/should_honor_union_generation_config/expected.kt new file mode 100644 index 0000000..5af5814 --- /dev/null +++ b/test/unit/should_honor_union_generation_config/expected.kt @@ -0,0 +1,28 @@ +package com.kotlin.generated + +import com.expediagroup.graphql.generator.annotations.* + +@GraphQLDescription("A description for TypeForHonoringUnionGenerationConfig1") +data class TypeForHonoringUnionGenerationConfig1( + val field: String? = null +) + +data class TypeForHonoringUnionGenerationConfig2( + val field: String? = null +) + +@GraphQLUnion( + name = "UnionAsAnnotation", + possibleTypes = [TypeForHonoringUnionGenerationConfig1::class, TypeForHonoringUnionGenerationConfig2::class], + description = "A description for UnionAsAnnotation" +) +annotation class UnionAsAnnotation + +data class UnionForHonoringUnionGenerationConfig( + @UnionAsAnnotation + @GraphQLDescription("A description for field") + val field: Any? = null, + @UnionAsAnnotation + @GraphQLDescription("DEPRECATED: It uses the GraphQLDescription annotation for union annotations") + val field2: Any? = null +) diff --git a/test/unit/should_honor_union_generation_config/schema.graphql b/test/unit/should_honor_union_generation_config/schema.graphql new file mode 100644 index 0000000..f22283e --- /dev/null +++ b/test/unit/should_honor_union_generation_config/schema.graphql @@ -0,0 +1,22 @@ +"A description for TypeForHonoringUnionGenerationConfig1" +type TypeForHonoringUnionGenerationConfig1 { + field: String +} + +type TypeForHonoringUnionGenerationConfig2 { + field: String +} + +""" +A description for UnionAsAnnotation +""" +union UnionAsAnnotation = + | TypeForHonoringUnionGenerationConfig1 + | TypeForHonoringUnionGenerationConfig2 + +type UnionForHonoringUnionGenerationConfig { + "A description for field" + field: UnionAsAnnotation + "DEPRECATED: It uses the GraphQLDescription annotation for union annotations" + field2: UnionAsAnnotation +} diff --git a/test/unit/should_include_dependent_types_in_onlyTypes_config/expected.kt b/test/unit/should_include_dependent_types_in_onlyTypes_config/expected.kt index 05c4797..1895564 100644 --- a/test/unit/should_include_dependent_types_in_onlyTypes_config/expected.kt +++ b/test/unit/should_include_dependent_types_in_onlyTypes_config/expected.kt @@ -6,8 +6,7 @@ data class MyType( val typeField: MyNestedType, val typeListField: List = emptyList(), val enumField: MyEnum, - @MyUnion - val unionField: Any, + val unionField: MyUnion, val unionImplementationField: UnionImplementation, val interfaceField: MyInterface1, val interfaceImplementationField: MyImplementedInterface @@ -34,24 +33,19 @@ enum class MyEnum { } } -@GraphQLUnion( - name = "MyUnion", - possibleTypes = [MyType1::class, MyType2::class], - description = "" -) -annotation class MyUnion +interface MyUnion data class MyType1( val field: String? = null -) +) : MyUnion data class MyType2( val field: String? = null -) +) : MyUnion data class UnionImplementation( val field: String? = null -) +) : MyUnion2 interface MyInterface1 { val field: String? @@ -73,16 +67,11 @@ data class MyNestedInput( val field: String? = null ) -@GraphQLUnion( - name = "MyStandaloneUnion", - possibleTypes = [StandaloneUnionType::class], - description = "" -) -annotation class MyStandaloneUnion +interface MyStandaloneUnion data class StandaloneUnionType( val field: StandaloneNestedType? = null -) +) : MyStandaloneUnion data class StandaloneNestedType( val field: String? = null From 079e2bcb347667a0eee9b113d39c0188dcfad7be Mon Sep 17 00:00:00 2001 From: Dan Adajian Date: Mon, 22 Apr 2024 12:57:41 -0500 Subject: [PATCH 3/6] inject defaults in codegen config --- src/config.ts | 9 ++++++ src/definitions/enum.ts | 8 ++--- src/definitions/input.ts | 4 +-- src/definitions/interface.ts | 4 +-- src/definitions/object.ts | 6 ++-- src/definitions/union.ts | 4 +-- src/helpers/add-dependent-types.ts | 27 ++++++++--------- src/helpers/build-annotations.ts | 4 +-- src/helpers/build-config-with-defaults.ts | 19 ++++++++++++ src/helpers/build-directive-annotations.ts | 6 ++-- src/helpers/build-field-definition.ts | 4 +-- src/helpers/build-type-metadata.ts | 6 ++-- src/helpers/dependent-type-is-in-scope.ts | 4 +-- src/helpers/dependent-type-utils.ts | 7 +++-- src/helpers/get-dependent-type-names.ts | 9 ++---- src/helpers/is-resolver-type.ts | 4 +-- src/helpers/should-include-type-definition.ts | 4 +-- src/plugin.ts | 29 +++++++++---------- src/visitor.ts | 10 +++++-- test/plugin.test.ts | 4 +-- 20 files changed, 101 insertions(+), 71 deletions(-) create mode 100644 src/helpers/build-config-with-defaults.ts diff --git a/src/config.ts b/src/config.ts index abe1033..cbd2b0c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -22,6 +22,7 @@ import { union, } from "valibot"; import { Kind } from "graphql"; +import { buildConfigWithDefaults } from "./helpers/build-config-with-defaults"; export const configSchema = object({ /** @@ -130,7 +131,15 @@ export const configSchema = object({ }), ), ), + /** + * Denotes the generation strategy for union types. Defaults to `MARKER_INTERFACE`. + * https://opensource.expediagroup.com/graphql-kotlin/docs/schema-generator/writing-schemas/unions/ + */ unionGeneration: optional( union([literal("ANNOTATION_CLASS"), literal("MARKER_INTERFACE")]), ), }); + +export type CodegenConfigWithDefaults = ReturnType< + typeof buildConfigWithDefaults +>; diff --git a/src/definitions/enum.ts b/src/definitions/enum.ts index c4e7a7b..b0836b9 100644 --- a/src/definitions/enum.ts +++ b/src/definitions/enum.ts @@ -15,11 +15,11 @@ import { EnumTypeDefinitionNode, EnumValueDefinitionNode } from "graphql"; import { indentMultiline } from "@graphql-codegen/visitor-plugin-common"; import { buildAnnotations } from "../helpers/build-annotations"; import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-definition"; -import { CodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "../config"; export function buildEnumTypeDefinition( node: EnumTypeDefinitionNode, - config: CodegenConfig, + config: CodegenConfigWithDefaults, ) { if (!shouldIncludeTypeDefinition(node, config)) { return ""; @@ -46,11 +46,11 @@ ${indentMultiline(enumValues.join(",\n") + ";", 2)} function buildEnumValueDefinition( node: EnumValueDefinitionNode, - config: CodegenConfig, + config: CodegenConfigWithDefaults, ) { const annotations = buildAnnotations({ config, definitionNode: node, }); - return `${annotations}${config.convert(node)}`; + return `${annotations}${config.convert?.(node)}`; } diff --git a/src/definitions/input.ts b/src/definitions/input.ts index 8d34a58..c8a102b 100644 --- a/src/definitions/input.ts +++ b/src/definitions/input.ts @@ -16,12 +16,12 @@ import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-defi import { buildTypeMetadata } from "../helpers/build-type-metadata"; import { buildAnnotations } from "../helpers/build-annotations"; import { indent } from "@graphql-codegen/visitor-plugin-common"; -import { CodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "../config"; export function buildInputObjectDefinition( node: InputObjectTypeDefinitionNode, schema: GraphQLSchema, - config: CodegenConfig, + config: CodegenConfigWithDefaults, ) { if (!shouldIncludeTypeDefinition(node, config)) { return ""; diff --git a/src/definitions/interface.ts b/src/definitions/interface.ts index 9072eb6..7c321e4 100644 --- a/src/definitions/interface.ts +++ b/src/definitions/interface.ts @@ -17,12 +17,12 @@ import { indent } from "@graphql-codegen/visitor-plugin-common"; import { buildTypeMetadata } from "../helpers/build-type-metadata"; import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-definition"; import { buildFieldDefinition } from "../helpers/build-field-definition"; -import { CodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "../config"; export function buildInterfaceDefinition( node: InterfaceTypeDefinitionNode, schema: GraphQLSchema, - config: CodegenConfig, + config: CodegenConfigWithDefaults, ) { if (!shouldIncludeTypeDefinition(node, config)) { return ""; diff --git a/src/definitions/object.ts b/src/definitions/object.ts index ad4c2aa..a268883 100644 --- a/src/definitions/object.ts +++ b/src/definitions/object.ts @@ -23,12 +23,12 @@ import { import { isResolverType } from "../helpers/is-resolver-type"; import { buildFieldDefinition } from "../helpers/build-field-definition"; import { isExternalField } from "../helpers/is-external-field"; -import { CodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "../config"; export function buildObjectTypeDefinition( node: ObjectTypeDefinitionNode, schema: GraphQLSchema, - config: CodegenConfig, + config: CodegenConfigWithDefaults, ) { if (!shouldIncludeTypeDefinition(node, config)) { return ""; @@ -70,7 +70,7 @@ function getClassMembers({ }: { node: ObjectTypeDefinitionNode; schema: GraphQLSchema; - config: CodegenConfig; + config: CodegenConfigWithDefaults; completableFuture?: boolean; }) { const resolverType = isResolverType(node, config); diff --git a/src/definitions/union.ts b/src/definitions/union.ts index 6f82f83..55947f2 100644 --- a/src/definitions/union.ts +++ b/src/definitions/union.ts @@ -14,7 +14,7 @@ limitations under the License. import { UnionTypeDefinitionNode } from "graphql"; import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-definition"; import { buildDirectiveAnnotations } from "../helpers/build-directive-annotations"; -import { CodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "../config"; import { buildAnnotations, trimDescription, @@ -22,7 +22,7 @@ import { export function buildUnionTypeDefinition( node: UnionTypeDefinitionNode, - config: CodegenConfig, + config: CodegenConfigWithDefaults, ) { if (!shouldIncludeTypeDefinition(node, config)) { return ""; diff --git a/src/helpers/add-dependent-types.ts b/src/helpers/add-dependent-types.ts index 4b813bd..a7da97a 100644 --- a/src/helpers/add-dependent-types.ts +++ b/src/helpers/add-dependent-types.ts @@ -11,25 +11,26 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { CodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "../config"; import { getDependentTypeNames } from "./get-dependent-type-names"; import { dependentTypeIsInScope } from "./dependent-type-is-in-scope"; import { GraphQLSchema } from "graphql"; export function addDependentTypes( - config: CodegenConfig, + config: CodegenConfigWithDefaults, schema: GraphQLSchema, ) { - if (config.onlyTypes && (config.includeDependentTypes ?? true)) { - const onlyTypesNodes = config.onlyTypes - .map((typeName) => schema.getType(typeName)?.astNode) - .filter(Boolean); - const dependentTypeNames = onlyTypesNodes.flatMap((node) => - getDependentTypeNames(schema, node, config), - ); - const dependentTypesInScope = dependentTypeNames.filter((typeName) => - dependentTypeIsInScope(typeName, config), - ); - config.onlyTypes.push(...dependentTypesInScope); + if (!config.onlyTypes) { + throw new Error(`config.onlyTypes is required to add dependent types`); } + const onlyTypesNodes = config.onlyTypes + .map((typeName) => schema.getType(typeName)?.astNode) + .filter(Boolean); + const dependentTypeNames = onlyTypesNodes.flatMap((node) => + getDependentTypeNames(schema, node, config), + ); + const dependentTypesInScope = dependentTypeNames.filter((typeName) => + dependentTypeIsInScope(typeName, config), + ); + config.onlyTypes.push(...dependentTypesInScope); } diff --git a/src/helpers/build-annotations.ts b/src/helpers/build-annotations.ts index bcea9dd..6beebe3 100644 --- a/src/helpers/build-annotations.ts +++ b/src/helpers/build-annotations.ts @@ -19,7 +19,7 @@ import { TypeDefinitionNode, } from "graphql"; import { buildDirectiveAnnotations } from "./build-directive-annotations"; -import { CodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "../config"; import { TypeMetadata } from "./build-type-metadata"; export type DefinitionNode = @@ -34,7 +34,7 @@ export function buildAnnotations({ definitionNode, resolvedType, }: { - config: CodegenConfig; + config: CodegenConfigWithDefaults; inputDescription?: string; definitionNode?: DefinitionNode; resolvedType?: TypeMetadata; diff --git a/src/helpers/build-config-with-defaults.ts b/src/helpers/build-config-with-defaults.ts new file mode 100644 index 0000000..500a771 --- /dev/null +++ b/src/helpers/build-config-with-defaults.ts @@ -0,0 +1,19 @@ +import { GraphQLKotlinCodegenConfig } from "../plugin"; +import { buildPackageNameFromPath } from "@graphql-codegen/java-common"; +import { dirname, normalize } from "path"; + +export function buildConfigWithDefaults( + config: GraphQLKotlinCodegenConfig, + outputFile: string, +) { + return { + packageName: buildPackageNameFromPath(dirname(normalize(outputFile))), + includeDependentTypes: true, + unionGeneration: "MARKER_INTERFACE", + ...config, + extraImports: [ + "com.expediagroup.graphql.generator.annotations.*", + ...(config.extraImports ?? ([] satisfies string[])), + ], + } as const; +} diff --git a/src/helpers/build-directive-annotations.ts b/src/helpers/build-directive-annotations.ts index 4c447ff..eb708b1 100644 --- a/src/helpers/build-directive-annotations.ts +++ b/src/helpers/build-directive-annotations.ts @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { CodegenConfig, GraphQLKotlinCodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "../config"; import { DefinitionNode, isDeprecatedDescription, @@ -23,7 +23,7 @@ import { ConstDirectiveNode } from "graphql/language"; export function buildDirectiveAnnotations( incomingNode: DefinitionNode, - config: CodegenConfig, + config: CodegenConfigWithDefaults, description?: string, resolvedType?: TypeMetadata, ) { @@ -75,7 +75,7 @@ export function buildDirectiveAnnotations( function buildKotlinAnnotations( directive: ConstDirectiveNode, kotlinAnnotations: NonNullable< - GraphQLKotlinCodegenConfig["directiveReplacements"] + CodegenConfigWithDefaults["directiveReplacements"] >[number]["kotlinAnnotations"], ) { return kotlinAnnotations.map((kotlinAnnotation) => { diff --git a/src/helpers/build-field-definition.ts b/src/helpers/build-field-definition.ts index ee71e0f..deb7b7d 100644 --- a/src/helpers/build-field-definition.ts +++ b/src/helpers/build-field-definition.ts @@ -21,12 +21,12 @@ import { } from "graphql"; import { isResolverType } from "./is-resolver-type"; import { isExternalField } from "./is-external-field"; -import { CodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "../config"; export function buildFieldDefinition( fieldNode: FieldDefinitionNode, definitionNode: ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode, - config: CodegenConfig, + config: CodegenConfigWithDefaults, completableFuture?: boolean, ) { const shouldUseFunction = diff --git a/src/helpers/build-type-metadata.ts b/src/helpers/build-type-metadata.ts index e45cd6d..d9040ed 100644 --- a/src/helpers/build-type-metadata.ts +++ b/src/helpers/build-type-metadata.ts @@ -21,7 +21,7 @@ import { } from "graphql"; import { getBaseTypeNode } from "@graphql-codegen/visitor-plugin-common"; import { wrapTypeWithModifiers } from "@graphql-codegen/java-common"; -import { CodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "../config"; export interface TypeMetadata { typeName: string; @@ -33,7 +33,7 @@ export interface TypeMetadata { export function buildTypeMetadata( typeNode: TypeNode, schema: GraphQLSchema, - config: CodegenConfig, + config: CodegenConfigWithDefaults, ): TypeMetadata { const innerType = getBaseTypeNode(typeNode); const schemaType = schema.getType(innerType.name.value); @@ -60,7 +60,7 @@ export function buildTypeMetadata( }; } else if (isUnionType(schemaType)) { const shouldTreatUnionAsInterface = - config.unionGeneration !== "ANNOTATION_CLASS" || + config.unionGeneration === "MARKER_INTERFACE" || config.externalUnionsAsInterfaces?.includes(schemaType.name); return { ...commonMetadata, diff --git a/src/helpers/dependent-type-is-in-scope.ts b/src/helpers/dependent-type-is-in-scope.ts index ab79b03..cad338a 100644 --- a/src/helpers/dependent-type-is-in-scope.ts +++ b/src/helpers/dependent-type-is-in-scope.ts @@ -11,11 +11,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { CodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "../config"; export function dependentTypeIsInScope( typeName: string, - config: CodegenConfig, + config: CodegenConfigWithDefaults, ) { return ( !config.dependentTypesInScope || diff --git a/src/helpers/dependent-type-utils.ts b/src/helpers/dependent-type-utils.ts index 51a4bc2..c4d0327 100644 --- a/src/helpers/dependent-type-utils.ts +++ b/src/helpers/dependent-type-utils.ts @@ -18,18 +18,19 @@ import { TypeDefinitionNode, TypeNode, } from "graphql"; -import { CodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "../config"; export function getDependentFieldTypeNames( node: TypeDefinitionNode, - dependentTypesInScope: CodegenConfig["dependentTypesInScope"], + config: CodegenConfigWithDefaults, ) { return "fields" in node && node.fields ? node.fields .map((field) => getFieldTypeName(field.type)) .filter( (typeName) => - !dependentTypesInScope || dependentTypesInScope.includes(typeName), + !config.dependentTypesInScope || + config.dependentTypesInScope.includes(typeName), ) : []; } diff --git a/src/helpers/get-dependent-type-names.ts b/src/helpers/get-dependent-type-names.ts index 08db1fe..8dc1651 100644 --- a/src/helpers/get-dependent-type-names.ts +++ b/src/helpers/get-dependent-type-names.ts @@ -18,17 +18,14 @@ import { getDependentInterfaceNames, getDependentUnionNames, } from "./dependent-type-utils"; -import { GraphQLKotlinCodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "../config"; export function getDependentTypeNames( schema: GraphQLSchema, node: TypeDefinitionNode, - config: GraphQLKotlinCodegenConfig, + config: CodegenConfigWithDefaults, ): string[] { - const namedTypes = getDependentFieldTypeNames( - node, - config.dependentTypesInScope, - ) + const namedTypes = getDependentFieldTypeNames(node, config) .concat(getDependentUnionNames(node)) .concat(getDependentInterfaceNames(node)); const recursivelyFoundTypes = namedTypes diff --git a/src/helpers/is-resolver-type.ts b/src/helpers/is-resolver-type.ts index a7ca626..54a9297 100644 --- a/src/helpers/is-resolver-type.ts +++ b/src/helpers/is-resolver-type.ts @@ -12,11 +12,11 @@ limitations under the License. */ import { InterfaceTypeDefinitionNode, ObjectTypeDefinitionNode } from "graphql"; -import { CodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "../config"; export function isResolverType( node: ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode, - config: CodegenConfig, + config: CodegenConfigWithDefaults, ) { return ( node.fields?.some((fieldNode) => fieldNode.arguments?.length) || diff --git a/src/helpers/should-include-type-definition.ts b/src/helpers/should-include-type-definition.ts index fecdf5b..a67b729 100644 --- a/src/helpers/should-include-type-definition.ts +++ b/src/helpers/should-include-type-definition.ts @@ -12,11 +12,11 @@ limitations under the License. */ import { TypeDefinitionNode } from "graphql"; -import { CodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "../config"; export function shouldIncludeTypeDefinition( node: TypeDefinitionNode, - config: CodegenConfig, + config: CodegenConfigWithDefaults, ) { return !config.onlyTypes || config.onlyTypes.includes(node.name.value); } diff --git a/src/plugin.ts b/src/plugin.ts index 25c62d1..947944c 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -11,12 +11,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { dirname, normalize } from "path"; import { getCachedDocumentNodeFromSchema, PluginFunction, } from "@graphql-codegen/plugin-helpers"; -import { buildPackageNameFromPath } from "@graphql-codegen/java-common"; import { KotlinVisitor } from "./visitor"; import { ParsedConfig, @@ -26,13 +24,12 @@ import { Input, safeParse } from "valibot"; import { configSchema } from "./config"; import { addDependentTypes } from "./helpers/add-dependent-types"; import { visit } from "graphql"; +import { buildConfigWithDefaults } from "./helpers/build-config-with-defaults"; export type GraphQLKotlinCodegenConfig = Partial & Input; -export type CodegenConfig = RawConfig & - ParsedConfig & - Input; -export const plugin: PluginFunction = ( + +export const plugin: PluginFunction = ( schema, _, config, @@ -41,7 +38,6 @@ export const plugin: PluginFunction = ( if (!info?.outputFile) { throw new Error("Missing outputFile in config"); } - const relevantPath = dirname(normalize(info.outputFile)); const { issues } = safeParse(configSchema, config); if (issues) { throw new Error( @@ -54,17 +50,20 @@ export const plugin: PluginFunction = ( ); } - addDependentTypes(config, schema); - const visitor = new KotlinVisitor(config, schema); + const configWithDefaults = buildConfigWithDefaults(config, info.outputFile); + + if ( + configWithDefaults.onlyTypes && + configWithDefaults.includeDependentTypes + ) { + addDependentTypes(configWithDefaults, schema); + } + const visitor = new KotlinVisitor(configWithDefaults, schema); const astNode = getCachedDocumentNodeFromSchema(schema); const { definitions } = visit(astNode, visitor); - const packageName = `package ${ - config.packageName ?? buildPackageNameFromPath(relevantPath) - }\n`; - const defaultImports = ["com.expediagroup.graphql.generator.annotations.*"]; + const packageName = `package ${configWithDefaults.packageName}\n`; const imports = - defaultImports - .concat(config.extraImports ?? []) + configWithDefaults.extraImports .map((annotation) => `import ${annotation}`) .join("\n") + "\n"; const typeDefinitions = definitions diff --git a/src/visitor.ts b/src/visitor.ts index c2b57b8..cd84482 100644 --- a/src/visitor.ts +++ b/src/visitor.ts @@ -20,16 +20,20 @@ import { ObjectTypeDefinitionNode, UnionTypeDefinitionNode, } from "graphql"; -import { CodegenConfig } from "./plugin"; +import { CodegenConfigWithDefaults } from "./config"; import { buildEnumTypeDefinition } from "./definitions/enum"; import { buildInterfaceDefinition } from "./definitions/interface"; import { buildInputObjectDefinition } from "./definitions/input"; import { buildObjectTypeDefinition } from "./definitions/object"; import { buildUnionTypeDefinition } from "./definitions/union"; +import { ParsedConfig } from "@graphql-codegen/visitor-plugin-common/typings/base-visitor"; -export class KotlinVisitor extends BaseVisitor { +export class KotlinVisitor extends BaseVisitor< + RawConfig, + ParsedConfig & CodegenConfigWithDefaults +> { constructor( - rawConfig: CodegenConfig, + rawConfig: CodegenConfigWithDefaults, protected _schema: GraphQLSchema, ) { super(rawConfig, rawConfig); diff --git a/test/plugin.test.ts b/test/plugin.test.ts index 6767630..ab1d934 100644 --- a/test/plugin.test.ts +++ b/test/plugin.test.ts @@ -1,5 +1,5 @@ import { buildSchema } from "graphql"; -import { CodegenConfig, plugin } from "../src/plugin"; +import { GraphQLKotlinCodegenConfig, plugin } from "../src/plugin"; import { describe, expect, it } from "bun:test"; import { Types } from "@graphql-codegen/plugin-helpers"; import * as glob from "glob"; @@ -9,7 +9,7 @@ function buildUnitTest({ config, }: { testName: string; - config: CodegenConfig; + config: GraphQLKotlinCodegenConfig; }) { it(testName, async () => { const filePath = `./test/unit/${testName}`; From af367f1996d51df1ee222c5eaaa2ef39f2f8a685 Mon Sep 17 00:00:00 2001 From: Dan Adajian Date: Mon, 22 Apr 2024 13:39:54 -0500 Subject: [PATCH 4/6] support alpha releases --- .github/workflows/publish.yml | 1 + .releaserc.yaml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c6f13e7..e1ecea0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - alpha jobs: publish: diff --git a/.releaserc.yaml b/.releaserc.yaml index cc2f6a1..6f184e3 100644 --- a/.releaserc.yaml +++ b/.releaserc.yaml @@ -17,3 +17,5 @@ plugins: - "@semantic-release/release-notes-generator" branches: - main + - name: alpha + prerelease: true From 3985645c7d7038a0c25cba72e8d606da31f0f1d7 Mon Sep 17 00:00:00 2001 From: Dan Adajian Date: Mon, 22 Apr 2024 13:41:39 -0500 Subject: [PATCH 5/6] bump bun version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 773e92a..a934569 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@expediagroup/graphql-kotlin-codegen", - "packageManager": "bun@1.0.30", + "packageManager": "bun@1.1.4", "main": "dist/plugin.js", "types": "dist/plugin.d.ts", "files": [ From ecbaed3879ea2a7b40560235979f34b1c7427f0d Mon Sep 17 00:00:00 2001 From: Dan Adajian Date: Mon, 22 Apr 2024 14:21:46 -0500 Subject: [PATCH 6/6] small refactors --- bun.lockb | Bin 236510 -> 237834 bytes docs/docs/configuration.md | 2 +- eslint.config.js | 1 + package.json | 4 +- src/config.ts | 12 ++-- src/definitions/enum.ts | 2 +- src/definitions/input.ts | 3 +- src/definitions/interface.ts | 2 +- src/definitions/object.ts | 16 ++--- src/definitions/union.ts | 18 +++--- ...s => add-dependent-types-to-only-types.ts} | 14 ++-- src/helpers/build-annotations.ts | 49 ++++---------- src/helpers/build-config-with-defaults.ts | 6 +- src/helpers/build-description-annotation.ts | 60 ++++++++++++++++++ src/helpers/build-directive-annotations.ts | 39 ++---------- src/helpers/build-field-definition.ts | 2 +- src/helpers/build-type-metadata.ts | 6 +- src/helpers/dependent-type-is-in-scope.ts | 24 ------- src/helpers/dependent-type-utils.ts | 2 +- src/helpers/get-dependent-type-names.ts | 2 +- src/helpers/is-resolver-type.ts | 2 +- src/helpers/should-include-type-definition.ts | 2 +- src/plugin.ts | 12 ++-- src/visitor.ts | 4 +- .../expected.kt | 7 +- .../schema.graphql | 7 +- .../should_honor_onlyTypes_config/expected.kt | 8 +-- .../schema.graphql | 8 +-- .../expected.kt | 11 +++- .../schema.graphql | 12 +++- 30 files changed, 170 insertions(+), 167 deletions(-) rename src/helpers/{add-dependent-types.ts => add-dependent-types-to-only-types.ts} (73%) create mode 100644 src/helpers/build-description-annotation.ts delete mode 100644 src/helpers/dependent-type-is-in-scope.ts diff --git a/bun.lockb b/bun.lockb index 3ae2d195f26cb0b9c9b209286ca7bb2cb871ffae..90df4322c5fb16582e052d553b7f100ab9c9dd92 100755 GIT binary patch delta 12289 zcmeI2dwh=d|HrTE+HQ8GF{jNM31Pz6hI_MSPC1lQrJ71IhRKW#8EKoy#135em{U$= zV$R%#C337xDn*6-`gTyMbTsAb_k7>ib!*>#9^e0ek4N{zYp>7y^?85ZpZDi*eeUbN zT<1O2UpiR5FrvlIWu4O-RiBXm=38Gkd2-4A?OV=NS)2G!X8OBF`-h*bHOp_xNp9DQpQfuw1?15P0OB}XLAl*4cjl@`Z%ytXlbFQ)x+Ebi@e6+ z*R9CvZL)eV(6kz;>)}9n1uS0ASekYloV(EGa`<-a=N~H(PEK~WZ>Q}=mvn{@M}{>bW#q*49 zwhVhh>Ns!Q<|}N!@0WXro0YvCK{_5bYH0fK@yQu*`cR^|fHvTA8OTE&Ka5nihg^ z5kVYdU$FxuP0UD2%ha^buqDBAZv!oRD=ZyvBA`sPzE0EX!rNcgv^sDMy2J}EwiCTU zz1VB8rT%m5`fwh8jj||=ryxRVa(bF1eRaK^X@B&_=pVgi2WpBf9R@K1;VT82)(}1k zi{H?Vb|Mq0myr%nPD-0Nc384+#4}6m1YFKQWZPV;9Xl(#V5c4MMOXs-NQLmo zv~kj}Hi3q9ut%q5B#ZqeiPXg2=dtJULu^^-qi_v)c-n;Iv6D1y1GWrgIV?MGdx342 zlxo^t*v zAi=1SZ|y1Qqz|wv3KGmO^EEAj1}-b7ncMt~m5IixY3Yx<&D!%ctqWFN38oLUDhdx0ai|Px48>Tx-+aU`EK(YtVAr`I(e$y((>g;WvGt8}Dv0@h` z7_F2wWl<+{q_V5%hiunj*+C?~&$0T^Ot*4cxy_!Ou>-x9KF@6~#ftG+`gyBDC73me zY%Rb#@TA+!!0Lzx6WrEqmS9C-xx{k)iq*lI);!K^x#VV?7H)GGmMjsog|!e%27$%w zp0z5963kGJ=2mFTQeI2|hCP8gJ75=2icv!~N#9q8dUXJNJTCUbzj zatNz~w;QRuh9zm!X`$O}$H^_rpjiysm5xQ)ZQ{&gN_Ni&R>xQs%M#4CD}3$fT_0^_ zE>AG`qshD*)`4)hc>_!OAy!AX+5TnQ-_N@h(y%0Vm!&s%yXvo`&MJ$HGascS{Rdcj zrrTVBb+6Y;HpTB)Nmzz=YYpNn!Cny7*j$Ds?KJOrKgN2%8%1(EZ)L7bFq*8f_N?q= zj^SLCT}Q{v@g^&FRf1V>t)}%yyUm;0NUZz4p}G{f%_~^Fu((jV#Q5`V@wj&xH&T)r zMNV~_-(hw3T5Kv;t5>-et?Mo0T&a`>%0+D+pd=o*$%SJE^DWWcYcYEBaV)uZIS8

VTUkC*_A!CrS+w2N5rSR67d-DZmo+$>;m^^@9IEIEKUP}p!K zSdZBjb^d%mNfi6AnS>=BRQ0wi#gaL%;=Shnz_J&P?Po-nSbK^)nMHgX$uYz3nC><& zVabJPcjVrrX}z)7N9>6LEZK?7e1zLPjb#Uui!N}pJ(GAgckBJE%-0h1_pQ>`63mb- zzTK+#cAF1k$?Dm&HxtXb9wOZOUaNFNg3Es^t6*K9A7}QVBy-D-Z0HLqdGR%H6IVz&D(0%p)PRxGP5I?h~6$-eBm%y65{c2qujR$?W3 zBgi~o#p+|nW9xM1dr{_6x4O99t|eIQtZB{S%<`MMtYXlvn>nyH{ji)%94q%PtG(NG z5UZ_hY1dDb26#7XSC4(7b9TAiW;)jW^jOuqA&+3mhQwlY#%_DFvd(0BKb9zq-NpCED{k{iEPHBYcb&i*gg+BaUVZjx+F0v)*En+zCHrEQ zOUJd>x9g<;C$WZj+en1HSaKop%|$kUVm<7&i^;_+hG_LQa6G>(qNS1cq^_!{j*NJunZs@mJV|qp6=A&ilu5Me}r?h z9LHQ(0?c-7VM%0;W8aF!e=hanQotXv3mt!9vFADVtvHbSM^-qFFT*Z`wXjTSvC~jk z0`BxI;q2c24BvEOv;TX7ZYk5eyMl*8hBia)Z`&WPc# z_?;Kqx_%@iD}cfkEQ$V;<0UMuDjfZq!+*dMT?Q!Zf+botSjJpK{``%_ua2WvX3<0a z*}u~99gag~7KeuD!gs^s*AylPE!^=J7QKa|w}NY9Cph($S)^``{ui@<&vxU<}H2mNUQRThhAe6bnV|Y{td5Jm&WLi zsl};!pz52dKcH_=MX3nA#v)u7p+xl_i*QwhlCcPzRfPz}X$XVU5VorIX$Xnq5CX>` zY*&NEAq0#^C=+3)GRGtA79n*!f=88#Fe)9PK{`UIN=Zkkmw|9XguN;>1L2qm(=rgs z)Nv6ePe5or0b#$&oPZEM5#gK&2ULrR2xmmdn~3nHIx9l%B!u`$2#3||NeD5|B3u^X zsEU0S;cF3=KZ|f&T@qpOWQ4wx5#ClslM#AlB3u{YgzBA%a8-noOoWrFLWJTe2!p2} zoKowjAS6yj2%L)Wz8W+YA>cWLG7(NI^ErgwBBVZt@R2GNVbnB)2GbDEs+4I6^`1vK zA;Kpr^m&A1B20T8;hZ`y!sIN3)>#M_RAv@Jcs9a05iY70*$8Ju$je6fT%8pmHwPg; z2jL4fI|m_VI>KcUzEZK%5xy2-`E-O!>XHbHUqI;l0>Zbd=mmscGZ3zea9Q=9fpArX zk{Jj;s0tB^XCe%qiEu@&pNWw8B0}Jc2tTVqFCqlYLMRjAS7pvZ*eycpEQH@xsR*NT z5gOzoRH&3(gnF|PPKfY_3Z0E`OoVB(5w5G_B20b>q4i4$H&o_J2;p-O&WWI_7IP5J zh>$l2!B3qPA$Kl9{9FW=nmrdGCJ*7V2vt>V9>UimEYCwQ)g=)Y=OgsZN2sQX@)3F! zAY2zAQ1vcAxGF+P0YVK`AwqE>!r(%LnreL^LgGAxz3gw_iY8mi2N2;qwm z&WX@SwOE93MufaY2u;*k5poqmyh3QIW-El4#R!*0Xs%)xBYZ8w^2G=()FlxX7a{a5 zLTIUqiV%7&LAWkLYt?%R!c`GUmLRlI6(STbMHsvkAyTbhijcSrA#fQ&v>LPwAz(Q| znF#Hbxg24)2&u~v+^STBQ7aG{tU!oWDJu}_y^L@|gg6!YGQu$troD_1ua1i_c_l*Y zl?Vwcb0tFfDui<)bWtrXmuP-Zl9YuT3ttzTjth|7VKzp z+a0BHx)`;MpA5CAi;>jg1>#YwHQ|r2+<6a^KKL|sn#s@K5voyFqjr2-s-!mDaj!{v zE4@et_c&TD%5w81pH@x?`H7RN#uK9M15QYTU`E@Bj zTjL$AF=hElA)ii;)`W7Z+S-i-zr|g~(3vX!X-$EIkTG<1+?zRC5Zuktnv16N?o3UH zzY6YdY}5C6j(0ci)yB|oY1Kt3n{gazQ2QGs@>G5f_!9cDR`R(2rbOBw# zDSX}or@=2ko)v_F`ru9=4;O+#2$07m7PG)ZNUfQ{4v?_;#-El&x$gZn@a&=bg`hXZ&npoLt=^1MeL9?1ix z>EH!017w5YU?6xz)$MCsX#EgP9|i+~Jb35>`hsR)Hf}G07r`u$qZ;)y;`CfKxStVL zSI(IcKn@T&Fp9tcmDkT`6S;@tUhpmOmH*Do$#V|%iJ*tN+|O_~E~EH4L%0Hb?xOh+ z>aTkAH#%|eIJ3WzXg){;KEDi;tGIFXO{`rBSPzPIPw)U^Pc_xIe4i1Ai!2!^X%I@GGvsx%54;aP0mEtY zk;7-%=w$@MJ3h+n!5&i+ls>&+JN99NbrH+Pn9~vXl?`=>iHo?u-Z7p2+9h< zUv7Y866EHnCXkyN-=xTmkR&cQKMg>A5b9|4Drt9A(#2nV!W=)bE7$whKgQsibmJr= zlbx6jI$*a4?LZWW1ofzI1k1L*3zmQlVHucwJ=_iCHmfm^b60MFT7njUBVTI^Dr;@9 z<=hK5xF*EOSJ|4f+*rvCmoI=XpfrjGlBB>_mgRjINHVfLWm|RxvLZ4tN!GW-V<}Gs zanxtR6IJX`BSi109vo_f86E9~36&_7TTA3a;7_$2??!Uh)B|(}a+4)DUs4Tu_gWWq zdZ^)6cPAP3xU~*UGD6h7Nk-e-!iS3oGjc&qN;2~GNRMln(WZ~5(>!B>UzqPLw|Q+I z&ug|bKGx8mk7^qc85t2hSM}d)gx3GR4l;82_~ok^n~f1h(*pI~W;*SrTw9Fj_P+OJ z&h6j#>zn?aHW>O-F%fMe+A;+N_8T=nUa!9|YV+`jziO7K)GbD6nD3pOOM^N+`@w); zpZ?3?q*{doKYsOnMyL@~sOs-C>KRQ6)sI`~IYIT`XGH6XDt;?8T8)Rod@oAf^Y8nc zrmd;d-c0gnr7?a=Y=MuD`CP z?I1hfyLjJC2%i!=r)ggTMny!*z>?Hf9Q0?@+dGVsfQgpX}`n z`>1+ZtgqGOUEaE?o||F1eecVp;*1@$ zyuC$6L~<{{w<^CMvR6lc*ze_6D%?&U)O1qU_YiNd3faqQe6OionJ@>vmrGqoJ>Jlv{3&Fy9+{Z3`0f2Ym3aNpu`*ufMZhy!#|r z740+J_OeIw1j|)swA9b5$TH&*eSw-)X5{D}EAw^cWS)wDopoxu+WY@Y*{kADX1o)U z)Pb-pql`30jmJUnujUAiS8=DHJhh+3VVj&*dlC;t1+Mb^BC{aPqO`dxa6f*DD)1fX zfaBhAh8&c}X#_)Dnca1u~--u%BYQDi$;aaO& zy!R2PjuV-XQu#WkF%SS0RJCJ zsfz^E?Ex{*YS}>}LUli2+-YYP%|l6bz`NndGg@Wm8?{x!QKPEbG{$Hb=6eS+<5HiO zJ{sTPP6Er!lfVs?bC68!h*3sxv3mEQk zRv$9@*@13O=2g}7@XaLk9JTH+o!G6ye6L+DzB1_BMf2;vO^)p&BAHmtiP!ar(ZjfN zgL>(RaTf`DHXbnyT_5V%eAMvM!+bAK&Odm4(uMBXk9p@#T5I0LsG}|(Ga81uSc%%~ z{h-A6r_`-mYb{>RQ8-hD9cPBiRr}+t#6|pq@cZISi)Sv}yQsD|n)m9-Rxcc92=mn` zabKrW--7n3Rd3PC_u}STnah9s^l0#UZ!331tfp0VkG9?W+7zr@ZyV1z5$tm_SnYk= zXwcsGUgWm?!+l(jywQqivdtKg@6E|GuPsiC>bqo@x1Y8V(NUUr(~b=Dy=u99RCw#B z2mZMN2j`^yP!-6o{!*=fhf(~let*XpY23a|J$-_0`=zo@p#GpPp1|k4`a|eP)%PS+ zp|+N@QL5}v@07EpeQ%Ne=*cZC3iu+%n*xU~uY;;-INq{Z6i4O_skB4;g8iqj{-Vl} z4hz)Mlk5(=-!R|Xso!mmUsY%4BjMiu_)g^Z+Lva7cX8jR9(El=Ghi8{X%t3V+|2ZkLxDO;@X4A24V3F`*WG zVDyu#=wBZgOE_?{PIEB%-ZQN;JY-$H!^v~WxjhTWfj>*_{fHs1RwwY&kEyRtbKZXI z>Hi@+LHAZJ)d#7^&KQ~c1kb56#vuK+aTn~HhAp3|b{`v^)#Q(jce9q{+b7Ycpv&L& z%*@VOi_K%8>?IppRr&42H+TNMemnKS)c>40GIc`vtL1;MKTf?|9z#YOQxCQ(Y4fQQ z|Eu7_+#%0x_k8x5okaRdxfRkpM?W$8=c$4yzbfizxLwZ4>pS~ZzJnQ+cXU%J`cfZ@7 z@nN|yiptH2sQ={;^z57n5B|e&b z+UC7b-B{Oh)0lRFk3ENPYFbYBT-zpLAHqM8YkeJ<5IT8|riI{+#R^V$>d=FZ! zfPYtMS|#kr)U?zAL;6~m$_44otUKlEMHMcxYkUVQCHuYX9jO+y#O`4xR$3hB*Z?c} zk;>Uq-?ZCnKq=|>z#)T1jZDqZs^H7GGSh~8tGStP$L++*xU!ept@Iz8tJhjrdjYQ;4gHjmL_gmO3(B`g~xG-O(_3E%?f{ zc0D&3pfq@Xwr%%dtK%=h*2*T)Xub4A%W1_3WuyaA`=yT>)<4x9aq*T-6XS)fx`8OGqg8R?^kY1%=@ ze`%||(9dC|JGh#>?2qi-i=wBRt;OU+aAz7 ztmF^FO8KtXFlIKV&ij(kavB3_W5Wt}JlZy}(7%Rny57RdZ5F5ZwWiNb)aP5pvlEST zxmLiO1igyYeomsfnKlwB;%DX6^_c5f@ff@yE9ga!c@eJ_Ua%F^(4*I|+UF&jUGg+7 z8s=~1)$^DW@tWWnR`M*5-!{DNR&KpGv)o)wOMvTEX&sN*(eZq(JiMiNUC7hC-Vb_Ew@f$5%LzIuy<2MoSDK9@j zO4>H8yv80sW4@+6DcrA@n=)6CYH3GAc+7tYck3}DIrg6Z&CA4-NP6Diqwle%FGw_Q zFR+d-NHAm6ZJ#ytc)UZuc|lD*X7HQ#Z2DQHZ9HaIye8hxl(IkO z;5GBMBYB7Lq}#fcJjY{d9Lur_n#G{~67cAD<2W;u)NiY%AF$fzCz`*)?2_z3zC4Oy zGncy4TN>^$kKsv6nibT-W9rN7cvhatj>D7Q`&mKtJbsrY&nk(EGixokd*E*cWqHh@ zcu#tx*cIk}ync9w>?yMj2ckWdtg<;6&+fU5Y874wBH8csZLL+jJkhwm!U`xzFq?DW zKV?_RG>^5W7bKd0hjoSpc)J$!mZm+6$5x;1G56rL!{ed|_xM%g>)=K2N{%5VlL}As znA`DW%dwRC&3$oNS%D4X{9;$JiR4l?b4f`+fLt-=*LX7e02#emm#+l5Vok3%2u~U$ zk6m#UPiBDYqM^qxoG+2KR!NUIa}p`3!N*(QVZ3C!2Fir+74i}uor(468J4*!(JX~U z+l>9;!k8Q-3cnPl!9`nvND!{WVldJA5o=hZ>^*s7xR`J?IeVJvhOEkaP=x)}a z_8zn9d-mGdlb4L=ToCMtsg}7u(eEUzot0$8nIW6(sbyUCJjSI>*3tC|X2fQ9Hsx&W zgZG@bOPsjoAw1cM%s!rPp?eK5e|{~*vr1y(%;BV*>+W@r`4e6zZy7myUf#lqYFs z+V6c*UA%j>T#p{f>wFV@Ez{dGB@TTycF`7X!;ehQ`3f7fvw}rDWvRcS+1N? zycg^!+7I8QX}!Ik)G!rKmY>s--Wh<`RGf(WcH<9;rHAD8;o0WdVddyLH{q1s` zGH39Tyq@G`yzhEa=UzN%k5h{)t9oJawsC>khNfx%i`xMamS`C_Hq zlUOO2;P{D7epxH|t$b)-@}71Iv~dFOXCEQ%c_&}2jGz}*8tm=ZK2HAqtYr1)kJy1u z+`w!SQo$exh?SlVas2yPi62V7grxCD{9#VKSn<;x|9+MmQ|(nJE_*x)Kfpw+Oz9M- zpjfGBs^g0-hd&=H9aIh%D{%`QzS!YnrQT&u{&I(lmG}bN&vp}kun!RM4gpfZdaT55 z5dOpLLZRE94q`mU#ZwyG0Q)(WcW z@cUV5s4n?34-K(WaU;h@U?o4s@ta`f^SiaL$uhvMpecdUvF1(zv9c3dV5NaL$B)NK zTuZEc?q_9T+L14|i&O47tSn4QHxknG-dO4B0LKo&$`%}rl?KLQB`(LYQylvSmVeqD z$6tW;DZIO_?O&&zJ#l}Xc7L6Af1P%m5OV7Mf1h@8{QaMvc7<8z&gZEOX?h2Jjk=Sj zzo2fX>4B=}Fu*#sW*A`gFhF2BV1r6Y2Xs#d>=Ce0nZp78!vSf-0h`ng0ow&s9RVm* zLq-4wj{uYi*s4ND0zyUt#*YLPsbT>~1T@M3>`+-5fH4_>3j%hj`k8?6Oh9fXV2?U0 z;EaIyQGgHB^ihCmqX5?g>{GF?0Ghu7$bSW}UtJM!SwPZgz(KWWG+^Oqz+C}{Rr@i3 zc4Gh=#sG@d9Rarm^vnVrQ){vStFr)sV*w>9Wh|ikSil|uCzLr3;6DzKHV#m#b_m!m zpz5oDkJXS@0fS!!lnD4#g^mY=j0cP#4>+xg1soC3=rzDumGv56%xi!P0?w)W*?{nD zKyEhRf;ubUjDYwYz(qAZ2QV!Ma81BhDs}>(`2;}z1i&}yih#=kk|qMaRf{G97ET1* z6>v$lp9E+(39w-j;EK8<;I@FCuLHhUYhDMeejN}v8E{RdOa^qH4A>*!x-zE#{HFlY zrT}iJ9Rjuss5%vJQw^C47(5kFBH)$^odyV*1{gmL@QW%Ia6~|(>3};bYdT=ebif4x z|5o*90K#Vga%TYUsJSHOd+{VYJcS%3|*0H(Sl;I@FCvjGpOHM0S$X9EJ~00LFY96m`U1eT1%PV;YN}WTXs!VH3h=nP zBH*%sq=kSwYSBW#!i9jl0_v&uivaBw0X8fG)K_-|+!oMtF`$83vly^?F(7aWppi;h z0_eU3utz{+WxfgUe-n`QCLl`f5U^c9)qFsV8j=qfoDV1w&{Tyk1%xaGj9&`ysA2&} z1TCu^tyJs^K=T!V z{1t%K>WYBN0+Ln&o>7Zd0v4{+1B>pi)ce;|TlVP#RG(eCZ_)XKdZwW-D@s11*Ubv} zv8CMka+}#kMP4*2sza@e2I`-!jPTT7?F{WvtoJ^jsCy2p;4ry4^^rSRJ{27%H@dzK z3wD@%Ej;9~N)D4dL95?#_*IAUe{se zv8^3ePngo5VQOyqs7lWmoArf7#m^W|>Si0MN7K%CR^{3n|E2uf86EWimE6v#mtB#X zgHa_^8HJ$7&`%V(iH@;~AE6R-9GyTXQ7JlwK1QFS&(LXf2AxHpqjTsyx`4hw7txpK zEA%z`yUu^Yd`sdV=n}e&uAuMG_voswUTJ5{&6b}&e?u>$G&BsQqmd{Br6T#cl!9dG z+n{e4#5wdZ=|B{O9z_*UMHGxGp~@%}g`sK0Pe(IRE}DZZc}l|*S*-_>pTj?(>*z;x z1M%oh<58M-!^rcD95fQiLzH%?J$e>(KuM?*>WsReWYiUPLq9WM`DyYDs)xdn?6fC* z8fsbt5_M4rmZT$+XHiv9Ra6btKsC`VqbT}W<2yZjD#ICrWJAhkdlq#-NAQoLW9U~} zOn}KxSGiK;H+D5t9o0Y^iFpqdqEqNLx`Y0Oens*S;GgIgT8=Dcfro@zU4zG?lK`(H zc?cyBrsR>$-_XlQo*#8bDX1rUokEjQBIzjgX-DI*zF6fa8I9Y^&-+^Faa0@CK|CYy zK3ADX3Au{pS&cmSNke^5U(^rD)2XLWG>TEdos5f(A}Jb$qLDlycmg#*zGx6(gV6w# zih8S>osBqspz7J#2n&`|=4m9yha4B9QH09vY&4EqNOBR93)fBm+18_ZF5+N#r3V-j0XA=-?XENu+-U38BR!_zjAUV@E7JVlrP{nXxrZA6mh zKW4P9q^03)*u&@$`T*@hJJAlb9Z8(jEA8yo8S49hJ?H@X5bZ^+$$T2^Bi#(UANv{_ zj}D?ENE$91BgfuRB=tx~q%JwB>x~zH8~FDSdec`9eAoS|}-PJwe~de2Vx9Q_90UB0TaesWQ}>nH0b zYb0lDp&HTK2+Nk^)q}!O4)w_~E5~gIbQSqi#a-+JSRZ5{*$V%H$=14oenh^o>)0QV ztg5Vne8j(renNN9Z6qh#KM`fJwOb^9LDI;-kyP$hCKXBl|IA!p@^w0NuT0thke2e2 zCvi_8`Q9vBr*J!tC6Zpd!7?IuH10?wSSpZ_`bkB9s!T2}iIK7olK1EGQg=7x4(=N1 z-#VzwzD5Q8VO6y+-@S5EBR5JiKS9{?NN#@I`H~wZDJeHeRZtiTaaiRtEVK-lc!_z; zi4(tUzPmL%25ZrbAQ`4?(vheseiIafqEI9XA-@Jzc6@cLR9Fow!>o;!+pb!uCX!RG z5t92TIho~4r7 z>KP)5ZjFyMbnnil+=*P3v&9$~=Dw^l)&Jy%MZOb~482#A zi0FuD&TXY{HDbctw_7fJu>012--LApG>?dkh~)pFRp+fnXjAufm;mGMq;kQz_kIs> zU!b{?k}&#{F1Pypp0h;dQ4Y-P(Tf?76#PzsFslt*&l0Lih<;aMGxx z!nPSz_1Y?Sn-QZoQ`!4KlLWO=1>2~uliIb-7^L@5j~5Y;u3iu`R?RFTZ?Y=cXN0M} zMaDoqzo_1JBU#sHsD1Anp<(VTa#u6M$HvaABU2C^5hWvxP#bno)}s#ZFgEzLvbYgc zd2bjYHN3g*D|>#YJLs7!n&wb0#-5p>YST{2jZ()jVeYGWEe5X5ug& zKz#vk+HY*tJlD8ayujBpeWKN`KZJ#`E^|3M=xhnnu;fvLX14fc@dWG71 zzz8wETA@BaU_>)Xfd?sGy3+f9S~=yre>^rg_|dndQPzg?<5epHC?0j3pVR3Fjp{s_ zoPCfL@_XBUW37DaY3o1QzoMeIqmeSA_3DC@u?J+7tWsZ|<)V7`uo0qK9x|#?*;j|? zl6O8UsWpcTkBugTxvw#1Tx?@p$y z{{HaO1s{!Ov@vX9X3#s`G4@P{>K#?~aeC5Abw0sb$(xb*x^^**$wlYh(+$=Ss6mNx^DQ2^JyOb{PS2xKs&TLXW zPO;lItJ$ZZyVQkK#H>~KKEdo(K_6odstp+9RH2IegbjMJQ1$tkIdWgT{Its6^If+b z@a{~`QFg?A%c(CvW;ET@zokmM0k_IOHl#nN#!KrjDfJ0+VYeRUz8-pYV|+oytgs1k6XT~Hs@7@a zxSpkMoaVgqni%!PN5){c4%Oo<)q9H$a`w|=9yE^ooYi+<>#R7S@;f01Q{SM6O<4|p zJ# zs{J`XoBT@T-^?16mU(>D@jvI!C%*#uy@wj(K5V$5@oK03OO@tK>-}nB(dBb?Puj?D UPkzpQ@TJi?x9F*1K1ZVe8>7ejRR910 diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index 105da11..052ebe4 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -5,5 +5,5 @@ sidebar_position: 3 # Configuration ```ts reference title="Config Schema" -https://github.com/ExpediaGroup/graphql-kotlin-codegen/blob/main/src/config.ts#L17-L100000 +https://github.com/ExpediaGroup/graphql-kotlin-codegen/blob/main/src/config.ts#L1-L100000 ``` diff --git a/eslint.config.js b/eslint.config.js index 03d7dab..09c7cc4 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -13,6 +13,7 @@ export default [ }, }, rules: { + "no-console": "error", "@typescript-eslint/no-non-null-assertion": "error", "@typescript-eslint/no-unsafe-argument": "error", "@typescript-eslint/no-unsafe-call": "error", diff --git a/package.json b/package.json index 45be384..fd1913f 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@graphql-codegen/cli": "5.0.2", "@total-typescript/ts-reset": "0.5.1", "bun-types": "1.1.4", - "eslint": "9.1.0", + "eslint": "9.1.1", "husky": "9.0.11", "prettier": "3.2.5", "tsup": "8.0.2", @@ -39,7 +39,7 @@ "build": "tsup src/plugin.ts --clean --dts --external graphql", "format": "prettier --write .", "format-check": "prettier --check .", - "integration": "bun run build && graphql-codegen && ./gradlew compileTestKotlin", + "integration": "bun run build && graphql-codegen && ./gradlew build", "lint": "eslint .", "prepack": "bun run build", "prepare": "husky", diff --git a/src/config.ts b/src/config.ts index cbd2b0c..fa9f41b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -22,7 +22,6 @@ import { union, } from "valibot"; import { Kind } from "graphql"; -import { buildConfigWithDefaults } from "./helpers/build-config-with-defaults"; export const configSchema = object({ /** @@ -46,11 +45,11 @@ export const configSchema = object({ dependentTypesInScope: optional(array(string())), /** * Denotes Kotlin classes representing union types to be treated as interfaces rather than annotation classes. - * This should be used for types outside `dependentTypesInScope` that are not generated by the plugin. + * @description This should be used for types outside `dependentTypesInScope` that are not generated by the plugin. Only use when unionGeneration is set to `ANNOTATION_CLASS`. */ externalUnionsAsInterfaces: optional(array(string())), /** - * Additional imports to add to the generated file. + * Additional imports to add to the generated file. GraphQL Kotlin annotations are always imported. * @example ["com.example.additional.import.*"] */ extraImports: optional(array(string())), @@ -133,13 +132,10 @@ export const configSchema = object({ ), /** * Denotes the generation strategy for union types. Defaults to `MARKER_INTERFACE`. - * https://opensource.expediagroup.com/graphql-kotlin/docs/schema-generator/writing-schemas/unions/ + * @description The `MARKER_INTERFACE` option is highly recommended, since it is more type-safe than using annotation classes. + * @link https://opensource.expediagroup.com/graphql-kotlin/docs/schema-generator/writing-schemas/unions/ */ unionGeneration: optional( union([literal("ANNOTATION_CLASS"), literal("MARKER_INTERFACE")]), ), }); - -export type CodegenConfigWithDefaults = ReturnType< - typeof buildConfigWithDefaults ->; diff --git a/src/definitions/enum.ts b/src/definitions/enum.ts index b0836b9..a06e352 100644 --- a/src/definitions/enum.ts +++ b/src/definitions/enum.ts @@ -15,7 +15,7 @@ import { EnumTypeDefinitionNode, EnumValueDefinitionNode } from "graphql"; import { indentMultiline } from "@graphql-codegen/visitor-plugin-common"; import { buildAnnotations } from "../helpers/build-annotations"; import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-definition"; -import { CodegenConfigWithDefaults } from "../config"; +import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults"; export function buildEnumTypeDefinition( node: EnumTypeDefinitionNode, diff --git a/src/definitions/input.ts b/src/definitions/input.ts index c8a102b..ddca970 100644 --- a/src/definitions/input.ts +++ b/src/definitions/input.ts @@ -16,7 +16,7 @@ import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-defi import { buildTypeMetadata } from "../helpers/build-type-metadata"; import { buildAnnotations } from "../helpers/build-annotations"; import { indent } from "@graphql-codegen/visitor-plugin-common"; -import { CodegenConfigWithDefaults } from "../config"; +import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults"; export function buildInputObjectDefinition( node: InputObjectTypeDefinitionNode, @@ -47,7 +47,6 @@ export function buildInputObjectDefinition( const annotations = buildAnnotations({ config, - inputDescription: node.description?.value, definitionNode: node, }); diff --git a/src/definitions/interface.ts b/src/definitions/interface.ts index 7c321e4..bf302c0 100644 --- a/src/definitions/interface.ts +++ b/src/definitions/interface.ts @@ -17,7 +17,7 @@ import { indent } from "@graphql-codegen/visitor-plugin-common"; import { buildTypeMetadata } from "../helpers/build-type-metadata"; import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-definition"; import { buildFieldDefinition } from "../helpers/build-field-definition"; -import { CodegenConfigWithDefaults } from "../config"; +import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults"; export function buildInterfaceDefinition( node: InterfaceTypeDefinitionNode, diff --git a/src/definitions/object.ts b/src/definitions/object.ts index a268883..29dbcb5 100644 --- a/src/definitions/object.ts +++ b/src/definitions/object.ts @@ -23,7 +23,7 @@ import { import { isResolverType } from "../helpers/is-resolver-type"; import { buildFieldDefinition } from "../helpers/build-field-definition"; import { isExternalField } from "../helpers/is-external-field"; -import { CodegenConfigWithDefaults } from "../config"; +import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults"; export function buildObjectTypeDefinition( node: ObjectTypeDefinitionNode, @@ -42,27 +42,27 @@ export function buildObjectTypeDefinition( const dependentInterfaces = getDependentInterfaceNames(node); const dependentUnions = getDependentUnionsForType(schema, node); const interfacesToInherit = - config.unionGeneration === "ANNOTATION_CLASS" - ? dependentInterfaces - : dependentInterfaces.concat(dependentUnions); + config.unionGeneration === "MARKER_INTERFACE" + ? dependentInterfaces.concat(dependentUnions) + : dependentInterfaces; const interfaceInheritance = `${interfacesToInherit.length ? ` : ${interfacesToInherit.join(", ")}` : ""}`; if (isResolverType(node, config)) { return `${annotations}@GraphQLIgnore\ninterface ${name}${interfaceInheritance} { -${getClassMembers({ node, schema, config })} +${getDataClassMembers({ node, schema, config })} } ${annotations}@GraphQLIgnore\ninterface ${name}CompletableFuture { -${getClassMembers({ node, schema, config, completableFuture: true })} +${getDataClassMembers({ node, schema, config, completableFuture: true })} }`; } return `${annotations}data class ${name}( -${getClassMembers({ node, schema, config })} +${getDataClassMembers({ node, schema, config })} )${interfaceInheritance}`; } -function getClassMembers({ +function getDataClassMembers({ node, schema, config, diff --git a/src/definitions/union.ts b/src/definitions/union.ts index 55947f2..d7c55f2 100644 --- a/src/definitions/union.ts +++ b/src/definitions/union.ts @@ -13,8 +13,7 @@ limitations under the License. import { UnionTypeDefinitionNode } from "graphql"; import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-definition"; -import { buildDirectiveAnnotations } from "../helpers/build-directive-annotations"; -import { CodegenConfigWithDefaults } from "../config"; +import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults"; import { buildAnnotations, trimDescription, @@ -31,17 +30,16 @@ export function buildUnionTypeDefinition( config, definitionNode: node, }); - if (config.unionGeneration === "ANNOTATION_CLASS") { - const directiveAnnotations = buildDirectiveAnnotations(node, config); - const possibleTypes = - node.types?.map((type) => `${type.name.value}::class`).join(", ") || ""; - return `${directiveAnnotations}@GraphQLUnion( + if (config.unionGeneration === "MARKER_INTERFACE") { + return `${annotations}interface ${node.name.value}`; + } + + const possibleTypes = + node.types?.map((type) => `${type.name.value}::class`).join(", ") || ""; + return `${annotations}@GraphQLUnion( name = "${node.name.value}", possibleTypes = [${possibleTypes}], description = "${trimDescription(node.description?.value)}" ) annotation class ${node.name.value}`; - } - - return `${annotations}interface ${node.name.value}`; } diff --git a/src/helpers/add-dependent-types.ts b/src/helpers/add-dependent-types-to-only-types.ts similarity index 73% rename from src/helpers/add-dependent-types.ts rename to src/helpers/add-dependent-types-to-only-types.ts index a7da97a..ab97959 100644 --- a/src/helpers/add-dependent-types.ts +++ b/src/helpers/add-dependent-types-to-only-types.ts @@ -11,17 +11,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { CodegenConfigWithDefaults } from "../config"; +import { CodegenConfigWithDefaults } from "./build-config-with-defaults"; import { getDependentTypeNames } from "./get-dependent-type-names"; -import { dependentTypeIsInScope } from "./dependent-type-is-in-scope"; import { GraphQLSchema } from "graphql"; -export function addDependentTypes( +export function addDependentTypesToOnlyTypes( config: CodegenConfigWithDefaults, schema: GraphQLSchema, ) { if (!config.onlyTypes) { - throw new Error(`config.onlyTypes is required to add dependent types`); + throw new Error(`onlyTypes config is required to add dependent types`); } const onlyTypesNodes = config.onlyTypes .map((typeName) => schema.getType(typeName)?.astNode) @@ -29,8 +28,9 @@ export function addDependentTypes( const dependentTypeNames = onlyTypesNodes.flatMap((node) => getDependentTypeNames(schema, node, config), ); - const dependentTypesInScope = dependentTypeNames.filter((typeName) => - dependentTypeIsInScope(typeName, config), - ); + const typesInScope = config.dependentTypesInScope; + const dependentTypesInScope = typesInScope + ? dependentTypeNames.filter((typeName) => typesInScope.includes(typeName)) + : dependentTypeNames; config.onlyTypes.push(...dependentTypesInScope); } diff --git a/src/helpers/build-annotations.ts b/src/helpers/build-annotations.ts index 6beebe3..b74d737 100644 --- a/src/helpers/build-annotations.ts +++ b/src/helpers/build-annotations.ts @@ -18,8 +18,9 @@ import { InputValueDefinitionNode, TypeDefinitionNode, } from "graphql"; +import { buildDescriptionAnnotation } from "./build-description-annotation"; import { buildDirectiveAnnotations } from "./build-directive-annotations"; -import { CodegenConfigWithDefaults } from "../config"; +import { CodegenConfigWithDefaults } from "./build-config-with-defaults"; import { TypeMetadata } from "./build-type-metadata"; export type DefinitionNode = @@ -30,37 +31,24 @@ export type DefinitionNode = export function buildAnnotations({ config, - inputDescription, definitionNode, resolvedType, }: { config: CodegenConfigWithDefaults; - inputDescription?: string; - definitionNode?: DefinitionNode; + definitionNode: DefinitionNode; resolvedType?: TypeMetadata; }) { - const description = - inputDescription ?? definitionNode?.description?.value ?? ""; - const isDeprecated = isDeprecatedDescription(description, resolvedType); - const descriptionAnnotator = isDeprecated - ? "@Deprecated" - : "@GraphQLDescription"; - const descriptionValue = isDeprecated - ? description.replace("DEPRECATED: ", "") - : description; - const trimmedDescription = trimDescription(descriptionValue); - const descriptionAnnotation = description - ? `${descriptionAnnotator}("${trimmedDescription}")\n` - : ""; - - const directiveAnnotations = definitionNode - ? buildDirectiveAnnotations( - definitionNode, - config, - description, - resolvedType, - ) - : ""; + const description = definitionNode?.description?.value ?? ""; + const descriptionAnnotation = buildDescriptionAnnotation( + description, + definitionNode, + config, + resolvedType, + ); + const directiveAnnotations = buildDirectiveAnnotations( + definitionNode, + config, + ); const unionAnnotation = resolvedType?.unionAnnotation ? `@${resolvedType.unionAnnotation}\n` : ""; @@ -84,15 +72,6 @@ export function buildAnnotations({ ); } -export function isDeprecatedDescription( - description?: string, - resolvedType?: TypeMetadata, -) { - return ( - description?.startsWith("DEPRECATED: ") && !resolvedType?.unionAnnotation - ); -} - export function trimDescription(description?: string) { return ( description diff --git a/src/helpers/build-config-with-defaults.ts b/src/helpers/build-config-with-defaults.ts index 500a771..e58fd41 100644 --- a/src/helpers/build-config-with-defaults.ts +++ b/src/helpers/build-config-with-defaults.ts @@ -13,7 +13,11 @@ export function buildConfigWithDefaults( ...config, extraImports: [ "com.expediagroup.graphql.generator.annotations.*", - ...(config.extraImports ?? ([] satisfies string[])), + ...(config.extraImports ?? []), ], } as const; } + +export type CodegenConfigWithDefaults = ReturnType< + typeof buildConfigWithDefaults +>; diff --git a/src/helpers/build-description-annotation.ts b/src/helpers/build-description-annotation.ts new file mode 100644 index 0000000..24129b9 --- /dev/null +++ b/src/helpers/build-description-annotation.ts @@ -0,0 +1,60 @@ +import { CodegenConfigWithDefaults } from "./build-config-with-defaults"; +import { TypeMetadata } from "./build-type-metadata"; +import { indent } from "@graphql-codegen/visitor-plugin-common"; +import { Kind } from "graphql/index"; +import { DefinitionNode, trimDescription } from "./build-annotations"; + +export function buildDescriptionAnnotation( + description: string, + definitionNode: DefinitionNode, + config: CodegenConfigWithDefaults, + resolvedType?: TypeMetadata, +) { + const trimmedDescription = trimDescription(description); + const isDeprecatedDescription = trimmedDescription.startsWith( + deprecatedDescriptionPrefix, + ); + if (isDeprecatedDescription && resolvedType?.unionAnnotation) { + return `@GraphQLDescription("${trimmedDescription}")\n`; + } else if (isDeprecatedDescription) { + const descriptionValue = description.replace( + deprecatedDescriptionPrefix, + "", + ); + return `@Deprecated("${trimDescription(descriptionValue)}")\n`; + } + + const deprecatedDirective = definitionNode.directives?.find( + (directive) => directive.name.value === "deprecated", + ); + const deprecatedReasonNode = deprecatedDirective?.arguments?.find( + (arg) => arg.name.value === "reason", + )?.value; + const deprecatedReason = + deprecatedReasonNode?.kind === "StringValue" + ? deprecatedReasonNode.value + : ""; + const trimmedDeprecatedReason = trimDescription(deprecatedReason); + + if (deprecatedDirective && resolvedType?.unionAnnotation) { + return `@GraphQLDescription("${trimmedDeprecatedReason}")\n`; + } else if (deprecatedDirective) { + const graphqlDescription = trimmedDescription + ? `@GraphQLDescription("${trimmedDescription}")\n` + : ""; + const deprecatedDescription = `@Deprecated("${trimmedDeprecatedReason}")\n`; + return `${graphqlDescription}${graphqlDescription ? indent(deprecatedDescription, 2) : deprecatedDescription}`; + } + + if ( + trimmedDescription && + (config.unionGeneration === "MARKER_INTERFACE" || + definitionNode?.kind !== Kind.UNION_TYPE_DEFINITION) + ) { + return `@GraphQLDescription("${trimmedDescription}")\n`; + } + + return ""; +} + +const deprecatedDescriptionPrefix = "DEPRECATED: "; diff --git a/src/helpers/build-directive-annotations.ts b/src/helpers/build-directive-annotations.ts index eb708b1..e1c3908 100644 --- a/src/helpers/build-directive-annotations.ts +++ b/src/helpers/build-directive-annotations.ts @@ -11,48 +11,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { CodegenConfigWithDefaults } from "../config"; -import { - DefinitionNode, - isDeprecatedDescription, - trimDescription, -} from "./build-annotations"; +import { CodegenConfigWithDefaults } from "./build-config-with-defaults"; +import { DefinitionNode } from "./build-annotations"; import { getFederationDirectiveReplacement } from "./get-federation-directive-replacement"; -import { TypeMetadata } from "./build-type-metadata"; import { ConstDirectiveNode } from "graphql/language"; export function buildDirectiveAnnotations( - incomingNode: DefinitionNode, + definitionNode: DefinitionNode, config: CodegenConfigWithDefaults, - description?: string, - resolvedType?: TypeMetadata, ) { - const kind = incomingNode.kind; - const directives = incomingNode.directives ?? []; - + const directives = definitionNode.directives ?? []; return directives .map((directive) => { const directiveName = directive.name.value; - if ( - directiveName === "deprecated" && - !isDeprecatedDescription(description) - ) { - const deprecatedReasonNode = directive.arguments?.find( - (arg) => arg.name.value === "reason", - )?.value; - const deprecatedReason = - deprecatedReasonNode?.kind === "StringValue" - ? deprecatedReasonNode.value - : ""; - if (incomingNode.description?.value && resolvedType?.unionAnnotation) { - return ""; - } - const descriptionAnnotator = resolvedType?.unionAnnotation - ? "@GraphQLDescription" - : "@Deprecated"; - const trimmedDeprecatedReason = trimDescription(deprecatedReason); - return `${descriptionAnnotator}("${trimmedDeprecatedReason}")\n`; - } const federationReplacement = getFederationDirectiveReplacement(directive); if (federationReplacement) return federationReplacement + "\n"; @@ -60,7 +31,7 @@ export function buildDirectiveAnnotations( const directiveReplacementFromConfig = config.directiveReplacements?.find( ({ directive, definitionType }) => directive === directiveName && - (!definitionType || definitionType === kind), + (!definitionType || definitionType === definitionNode.kind), ); if (!directiveReplacementFromConfig) return ""; const kotlinAnnotations = buildKotlinAnnotations( diff --git a/src/helpers/build-field-definition.ts b/src/helpers/build-field-definition.ts index deb7b7d..ca7b22c 100644 --- a/src/helpers/build-field-definition.ts +++ b/src/helpers/build-field-definition.ts @@ -21,7 +21,7 @@ import { } from "graphql"; import { isResolverType } from "./is-resolver-type"; import { isExternalField } from "./is-external-field"; -import { CodegenConfigWithDefaults } from "../config"; +import { CodegenConfigWithDefaults } from "./build-config-with-defaults"; export function buildFieldDefinition( fieldNode: FieldDefinitionNode, diff --git a/src/helpers/build-type-metadata.ts b/src/helpers/build-type-metadata.ts index d9040ed..a704738 100644 --- a/src/helpers/build-type-metadata.ts +++ b/src/helpers/build-type-metadata.ts @@ -13,15 +13,15 @@ limitations under the License. import { GraphQLSchema, - isScalarType, - isUnionType, Kind, NamedTypeNode, TypeNode, + isScalarType, + isUnionType, } from "graphql"; import { getBaseTypeNode } from "@graphql-codegen/visitor-plugin-common"; import { wrapTypeWithModifiers } from "@graphql-codegen/java-common"; -import { CodegenConfigWithDefaults } from "../config"; +import { CodegenConfigWithDefaults } from "./build-config-with-defaults"; export interface TypeMetadata { typeName: string; diff --git a/src/helpers/dependent-type-is-in-scope.ts b/src/helpers/dependent-type-is-in-scope.ts deleted file mode 100644 index cad338a..0000000 --- a/src/helpers/dependent-type-is-in-scope.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* -Copyright 2024 Expedia, Inc. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - https://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { CodegenConfigWithDefaults } from "../config"; - -export function dependentTypeIsInScope( - typeName: string, - config: CodegenConfigWithDefaults, -) { - return ( - !config.dependentTypesInScope || - config.dependentTypesInScope.includes(typeName) - ); -} diff --git a/src/helpers/dependent-type-utils.ts b/src/helpers/dependent-type-utils.ts index c4d0327..f9da022 100644 --- a/src/helpers/dependent-type-utils.ts +++ b/src/helpers/dependent-type-utils.ts @@ -18,7 +18,7 @@ import { TypeDefinitionNode, TypeNode, } from "graphql"; -import { CodegenConfigWithDefaults } from "../config"; +import { CodegenConfigWithDefaults } from "./build-config-with-defaults"; export function getDependentFieldTypeNames( node: TypeDefinitionNode, diff --git a/src/helpers/get-dependent-type-names.ts b/src/helpers/get-dependent-type-names.ts index 8dc1651..60d3e49 100644 --- a/src/helpers/get-dependent-type-names.ts +++ b/src/helpers/get-dependent-type-names.ts @@ -18,7 +18,7 @@ import { getDependentInterfaceNames, getDependentUnionNames, } from "./dependent-type-utils"; -import { CodegenConfigWithDefaults } from "../config"; +import { CodegenConfigWithDefaults } from "./build-config-with-defaults"; export function getDependentTypeNames( schema: GraphQLSchema, diff --git a/src/helpers/is-resolver-type.ts b/src/helpers/is-resolver-type.ts index 54a9297..559ed46 100644 --- a/src/helpers/is-resolver-type.ts +++ b/src/helpers/is-resolver-type.ts @@ -12,7 +12,7 @@ limitations under the License. */ import { InterfaceTypeDefinitionNode, ObjectTypeDefinitionNode } from "graphql"; -import { CodegenConfigWithDefaults } from "../config"; +import { CodegenConfigWithDefaults } from "./build-config-with-defaults"; export function isResolverType( node: ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode, diff --git a/src/helpers/should-include-type-definition.ts b/src/helpers/should-include-type-definition.ts index a67b729..2187d1b 100644 --- a/src/helpers/should-include-type-definition.ts +++ b/src/helpers/should-include-type-definition.ts @@ -12,7 +12,7 @@ limitations under the License. */ import { TypeDefinitionNode } from "graphql"; -import { CodegenConfigWithDefaults } from "../config"; +import { CodegenConfigWithDefaults } from "./build-config-with-defaults"; export function shouldIncludeTypeDefinition( node: TypeDefinitionNode, diff --git a/src/plugin.ts b/src/plugin.ts index 947944c..09d5ceb 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -12,8 +12,8 @@ limitations under the License. */ import { - getCachedDocumentNodeFromSchema, PluginFunction, + getCachedDocumentNodeFromSchema, } from "@graphql-codegen/plugin-helpers"; import { KotlinVisitor } from "./visitor"; import { @@ -22,7 +22,7 @@ import { } from "@graphql-codegen/visitor-plugin-common"; import { Input, safeParse } from "valibot"; import { configSchema } from "./config"; -import { addDependentTypes } from "./helpers/add-dependent-types"; +import { addDependentTypesToOnlyTypes } from "./helpers/add-dependent-types-to-only-types"; import { visit } from "graphql"; import { buildConfigWithDefaults } from "./helpers/build-config-with-defaults"; @@ -51,12 +51,11 @@ export const plugin: PluginFunction = ( } const configWithDefaults = buildConfigWithDefaults(config, info.outputFile); - if ( configWithDefaults.onlyTypes && configWithDefaults.includeDependentTypes ) { - addDependentTypes(configWithDefaults, schema); + addDependentTypesToOnlyTypes(configWithDefaults, schema); } const visitor = new KotlinVisitor(configWithDefaults, schema); const astNode = getCachedDocumentNodeFromSchema(schema); @@ -67,7 +66,10 @@ export const plugin: PluginFunction = ( .map((annotation) => `import ${annotation}`) .join("\n") + "\n"; const typeDefinitions = definitions - .filter((d: unknown) => typeof d === "string" && d.length) + .filter( + (definition: unknown) => + typeof definition === "string" && definition.length, + ) .join("\n\n"); return [packageName, imports, typeDefinitions].join("\n") + "\n"; diff --git a/src/visitor.ts b/src/visitor.ts index cd84482..52e9082 100644 --- a/src/visitor.ts +++ b/src/visitor.ts @@ -15,12 +15,12 @@ import { BaseVisitor, RawConfig } from "@graphql-codegen/visitor-plugin-common"; import { EnumTypeDefinitionNode, GraphQLSchema, - InterfaceTypeDefinitionNode, InputObjectTypeDefinitionNode, + InterfaceTypeDefinitionNode, ObjectTypeDefinitionNode, UnionTypeDefinitionNode, } from "graphql"; -import { CodegenConfigWithDefaults } from "./config"; +import { CodegenConfigWithDefaults } from "./helpers/build-config-with-defaults"; import { buildEnumTypeDefinition } from "./definitions/enum"; import { buildInterfaceDefinition } from "./definitions/interface"; import { buildInputObjectDefinition } from "./definitions/input"; diff --git a/test/unit/should_generate_input_types_properly/expected.kt b/test/unit/should_generate_input_types_properly/expected.kt index f5bb75c..ac9316d 100644 --- a/test/unit/should_generate_input_types_properly/expected.kt +++ b/test/unit/should_generate_input_types_properly/expected.kt @@ -4,8 +4,7 @@ import com.expediagroup.graphql.generator.annotations.* @GraphQLDescription("A description for MyInputType") data class InputTypeThatShouldBeGeneratedProperly( - val username: String? = null, - @GraphQLDescription("A description for email") - val email: String? = null, - val name: String? = null + val field1: String? = null, + @GraphQLDescription("A description for field2") + val field2: String? = null ) diff --git a/test/unit/should_generate_input_types_properly/schema.graphql b/test/unit/should_generate_input_types_properly/schema.graphql index 0072b3e..996f362 100644 --- a/test/unit/should_generate_input_types_properly/schema.graphql +++ b/test/unit/should_generate_input_types_properly/schema.graphql @@ -1,7 +1,6 @@ "A description for MyInputType" input InputTypeThatShouldBeGeneratedProperly { - username: String - "A description for email" - email: String - name: String + field1: String + "A description for field2" + field2: String } diff --git a/test/unit/should_honor_onlyTypes_config/expected.kt b/test/unit/should_honor_onlyTypes_config/expected.kt index 9bea03e..6997a70 100644 --- a/test/unit/should_honor_onlyTypes_config/expected.kt +++ b/test/unit/should_honor_onlyTypes_config/expected.kt @@ -3,11 +3,11 @@ package com.kotlin.generated import com.expediagroup.graphql.generator.annotations.* data class TypeHonoringOnlyTypesConfig( - val username: String? = null, - @GraphQLDescription("A description for email") - val email: String? = null, + val field1: String? = null, + @GraphQLDescription("A description for field2") + val field2: String? = null, @GraphQLDescription("A `weird` description for name") - val name: String? = null + val field3: String? = null ) @GraphQLDescription("A description for MyEnum") diff --git a/test/unit/should_honor_onlyTypes_config/schema.graphql b/test/unit/should_honor_onlyTypes_config/schema.graphql index e21a829..4ae6837 100644 --- a/test/unit/should_honor_onlyTypes_config/schema.graphql +++ b/test/unit/should_honor_onlyTypes_config/schema.graphql @@ -1,11 +1,11 @@ type TypeHonoringOnlyTypesConfig { - username: String - "A description for email" - email: String + field1: String + "A description for field2" + field2: String """ A \`weird\` description for name """ - name: String + field3: String } "A description for MyEnum" diff --git a/test/unit/should_honor_union_generation_config/expected.kt b/test/unit/should_honor_union_generation_config/expected.kt index 5af5814..faab3b8 100644 --- a/test/unit/should_honor_union_generation_config/expected.kt +++ b/test/unit/should_honor_union_generation_config/expected.kt @@ -24,5 +24,14 @@ data class UnionForHonoringUnionGenerationConfig( val field: Any? = null, @UnionAsAnnotation @GraphQLDescription("DEPRECATED: It uses the GraphQLDescription annotation for union annotations") - val field2: Any? = null + val deprecated1: Any? = null, + @UnionAsAnnotation + @GraphQLDescription("DEPRECATED: It uses the GraphQLDescription annotation for union types") + val deprecated2: Any? = null, + @UnionAsAnnotation + @GraphQLDescription("It uses the GraphQLDescription annotation for union types") + val deprecated3: Any? = null, + @UnionAsAnnotation + @GraphQLDescription("It omits the @Deprecated annotation for now") + val deprecated4: Any? = null ) diff --git a/test/unit/should_honor_union_generation_config/schema.graphql b/test/unit/should_honor_union_generation_config/schema.graphql index f22283e..8756c26 100644 --- a/test/unit/should_honor_union_generation_config/schema.graphql +++ b/test/unit/should_honor_union_generation_config/schema.graphql @@ -18,5 +18,15 @@ type UnionForHonoringUnionGenerationConfig { "A description for field" field: UnionAsAnnotation "DEPRECATED: It uses the GraphQLDescription annotation for union annotations" - field2: UnionAsAnnotation + deprecated1: UnionAsAnnotation + @deprecated(reason: "when you have multiple deprecated annotations") + "DEPRECATED: It uses the GraphQLDescription annotation for union types" + deprecated2: UnionAsAnnotation + deprecated3: UnionAsAnnotation + @deprecated( + reason: "It uses the GraphQLDescription annotation for union types" + ) + "When there is a description" + deprecated4: UnionAsAnnotation + @deprecated(reason: "It omits the @Deprecated annotation for now") }