diff --git a/bun.lockb b/bun.lockb index 3ae2d19..90df432 100755 Binary files a/bun.lockb and b/bun.lockb differ 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 5a3a217..fa9f41b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -15,6 +15,7 @@ import { array, boolean, enum_, + literal, object, optional, string, @@ -44,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())), @@ -129,4 +130,12 @@ export const configSchema = object({ }), ), ), + /** + * Denotes the generation strategy for union types. Defaults to `MARKER_INTERFACE`. + * @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")]), + ), }); diff --git a/src/definitions/enum.ts b/src/definitions/enum.ts index c4e7a7b..a06e352 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 "../helpers/build-config-with-defaults"; 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..ddca970 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 "../helpers/build-config-with-defaults"; export function buildInputObjectDefinition( node: InputObjectTypeDefinitionNode, schema: GraphQLSchema, - config: CodegenConfig, + config: CodegenConfigWithDefaults, ) { if (!shouldIncludeTypeDefinition(node, config)) { return ""; @@ -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 9072eb6..bf302c0 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 "../helpers/build-config-with-defaults"; 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 56885d1..29dbcb5 100644 --- a/src/definitions/object.ts +++ b/src/definitions/object.ts @@ -16,16 +16,19 @@ 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"; -import { CodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults"; export function buildObjectTypeDefinition( node: ObjectTypeDefinitionNode, schema: GraphQLSchema, - config: CodegenConfig, + config: CodegenConfigWithDefaults, ) { if (!shouldIncludeTypeDefinition(node, config)) { return ""; @@ -36,25 +39,30 @@ 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.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, @@ -62,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 e0a98f1..d7c55f2 100644 --- a/src/definitions/union.ts +++ b/src/definitions/union.ts @@ -13,25 +13,33 @@ 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 { trimDescription } from "../helpers/build-annotations"; +import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults"; +import { + buildAnnotations, + trimDescription, +} from "../helpers/build-annotations"; export function buildUnionTypeDefinition( node: UnionTypeDefinitionNode, - config: CodegenConfig, + config: CodegenConfigWithDefaults, ) { if (!shouldIncludeTypeDefinition(node, config)) { return ""; } + const annotations = buildAnnotations({ + config, + definitionNode: node, + }); + if (config.unionGeneration === "MARKER_INTERFACE") { + 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( + return `${annotations}@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-to-only-types.ts b/src/helpers/add-dependent-types-to-only-types.ts new file mode 100644 index 0000000..ab97959 --- /dev/null +++ b/src/helpers/add-dependent-types-to-only-types.ts @@ -0,0 +1,36 @@ +/* +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 "./build-config-with-defaults"; +import { getDependentTypeNames } from "./get-dependent-type-names"; +import { GraphQLSchema } from "graphql"; + +export function addDependentTypesToOnlyTypes( + config: CodegenConfigWithDefaults, + schema: GraphQLSchema, +) { + if (!config.onlyTypes) { + throw new Error(`onlyTypes config 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 typesInScope = config.dependentTypesInScope; + const dependentTypesInScope = typesInScope + ? dependentTypeNames.filter((typeName) => typesInScope.includes(typeName)) + : dependentTypeNames; + config.onlyTypes.push(...dependentTypesInScope); +} diff --git a/src/helpers/add-dependent-types.ts b/src/helpers/add-dependent-types.ts deleted file mode 100644 index a5097e4..0000000 --- a/src/helpers/add-dependent-types.ts +++ /dev/null @@ -1,35 +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 { CodegenConfig } from "../plugin"; -import { getDependentTypeNames } from "./get-dependent-type-names"; -import { dependentTypeIsInScope } from "./dependent-type-is-in-scope"; -import { GraphQLSchema } from "graphql"; - -export function addDependentTypes( - config: CodegenConfig, - 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.dependentTypesInScope), - ); - 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 0a0d12b..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 { CodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "./build-config-with-defaults"; import { TypeMetadata } from "./build-type-metadata"; export type DefinitionNode = @@ -30,39 +31,24 @@ export type DefinitionNode = export function buildAnnotations({ config, - inputDescription, definitionNode, resolvedType, }: { - config: CodegenConfig; - inputDescription?: string; - definitionNode?: DefinitionNode; + config: CodegenConfigWithDefaults; + definitionNode: DefinitionNode; resolvedType?: TypeMetadata; }) { - const description = - inputDescription ?? definitionNode?.description?.value ?? ""; - const descriptionAnnotator = isDeprecatedDescription( + const description = definitionNode?.description?.value ?? ""; + const descriptionAnnotation = buildDescriptionAnnotation( description, + definitionNode, + config, resolvedType, - ) - ? "@Deprecated" - : "@GraphQLDescription"; - const descriptionValue = isDeprecatedDescription(description, resolvedType) - ? description.replace("DEPRECATED: ", "") - : description; - const trimmedDescription = trimDescription(descriptionValue); - const descriptionAnnotation = description - ? `${descriptionAnnotator}("${trimmedDescription}")\n` - : ""; - - const directiveAnnotations = definitionNode - ? buildDirectiveAnnotations( - definitionNode, - config, - description, - resolvedType, - ) - : ""; + ); + const directiveAnnotations = buildDirectiveAnnotations( + definitionNode, + config, + ); const unionAnnotation = resolvedType?.unionAnnotation ? `@${resolvedType.unionAnnotation}\n` : ""; @@ -86,19 +72,10 @@ export function buildAnnotations({ ); } -export function isDeprecatedDescription( - description?: string, - resolvedType?: TypeMetadata, -) { - return ( - description?.startsWith("DEPRECATED: ") && !resolvedType?.unionAnnotation - ); -} - -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-config-with-defaults.ts b/src/helpers/build-config-with-defaults.ts new file mode 100644 index 0000000..e58fd41 --- /dev/null +++ b/src/helpers/build-config-with-defaults.ts @@ -0,0 +1,23 @@ +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 ?? []), + ], + } 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 4c447ff..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 { CodegenConfig, GraphQLKotlinCodegenConfig } from "../plugin"; -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, - config: CodegenConfig, - description?: string, - resolvedType?: TypeMetadata, + definitionNode: DefinitionNode, + config: CodegenConfigWithDefaults, ) { - 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( @@ -75,7 +46,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..ca7b22c 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 "./build-config-with-defaults"; 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 2682774..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 { CodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "./build-config-with-defaults"; 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,6 +60,7 @@ export function buildTypeMetadata( }; } else if (isUnionType(schemaType)) { const shouldTreatUnionAsInterface = + 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 deleted file mode 100644 index ab79b03..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 { CodegenConfig } from "../plugin"; - -export function dependentTypeIsInScope( - typeName: string, - config: CodegenConfig, -) { - return ( - !config.dependentTypesInScope || - config.dependentTypesInScope.includes(typeName) - ); -} diff --git a/src/helpers/dependent-type-utils.ts b/src/helpers/dependent-type-utils.ts index d8d6e65..f9da022 100644 --- a/src/helpers/dependent-type-utils.ts +++ b/src/helpers/dependent-type-utils.ts @@ -11,19 +11,26 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Kind, TypeDefinitionNode, TypeNode } from "graphql"; -import { CodegenConfig } from "../plugin"; +import { + GraphQLSchema, + GraphQLUnionType, + Kind, + TypeDefinitionNode, + TypeNode, +} from "graphql"; +import { CodegenConfigWithDefaults } from "./build-config-with-defaults"; 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), ) : []; } @@ -55,3 +62,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..60d3e49 100644 --- a/src/helpers/get-dependent-type-names.ts +++ b/src/helpers/get-dependent-type-names.ts @@ -18,21 +18,19 @@ import { getDependentInterfaceNames, getDependentUnionNames, } from "./dependent-type-utils"; -import { CodegenConfig } from "../plugin"; +import { CodegenConfigWithDefaults } from "./build-config-with-defaults"; export function getDependentTypeNames( schema: GraphQLSchema, node: TypeDefinitionNode, - dependentTypesInScope: CodegenConfig["dependentTypesInScope"], + config: CodegenConfigWithDefaults, ): string[] { - const namedTypes = getDependentFieldTypeNames(node, dependentTypesInScope) + const namedTypes = getDependentFieldTypeNames(node, config) .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/src/helpers/is-resolver-type.ts b/src/helpers/is-resolver-type.ts index a7ca626..559ed46 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 "./build-config-with-defaults"; 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..2187d1b 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 "./build-config-with-defaults"; 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..09d5ceb 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, + getCachedDocumentNodeFromSchema, } from "@graphql-codegen/plugin-helpers"; -import { buildPackageNameFromPath } from "@graphql-codegen/java-common"; import { KotlinVisitor } from "./visitor"; import { ParsedConfig, @@ -24,15 +22,14 @@ 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"; 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,21 +50,26 @@ export const plugin: PluginFunction = ( ); } - addDependentTypes(config, schema); - const visitor = new KotlinVisitor(config, schema); + const configWithDefaults = buildConfigWithDefaults(config, info.outputFile); + if ( + configWithDefaults.onlyTypes && + configWithDefaults.includeDependentTypes + ) { + addDependentTypesToOnlyTypes(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 - .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 c2b57b8..52e9082 100644 --- a/src/visitor.ts +++ b/src/visitor.ts @@ -15,21 +15,25 @@ import { BaseVisitor, RawConfig } from "@graphql-codegen/visitor-plugin-common"; import { EnumTypeDefinitionNode, GraphQLSchema, - InterfaceTypeDefinitionNode, InputObjectTypeDefinitionNode, + InterfaceTypeDefinitionNode, ObjectTypeDefinitionNode, UnionTypeDefinitionNode, } from "graphql"; -import { CodegenConfig } from "./plugin"; +import { CodegenConfigWithDefaults } from "./helpers/build-config-with-defaults"; 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}`; 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_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_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_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/codegen.config.ts b/test/unit/should_honor_union_generation_config/codegen.config.ts new file mode 100644 index 0000000..8b31606 --- /dev/null +++ b/test/unit/should_honor_union_generation_config/codegen.config.ts @@ -0,0 +1,5 @@ +import { GraphQLKotlinCodegenConfig } from "../../../src/plugin"; + +export default { + 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..faab3b8 --- /dev/null +++ b/test/unit/should_honor_union_generation_config/expected.kt @@ -0,0 +1,37 @@ +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 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 new file mode 100644 index 0000000..8756c26 --- /dev/null +++ b/test/unit/should_honor_union_generation_config/schema.graphql @@ -0,0 +1,32 @@ +"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" + 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") +} 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