diff --git a/src/definitions/input.ts b/src/definitions/input.ts index ddca970..bbfd265 100644 --- a/src/definitions/input.ts +++ b/src/definitions/input.ts @@ -17,6 +17,7 @@ import { buildTypeMetadata } from "../helpers/build-type-metadata"; import { buildAnnotations } from "../helpers/build-annotations"; import { indent } from "@graphql-codegen/visitor-plugin-common"; import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults"; +import { inputTypeHasMatchingOutputType } from "../helpers/input-type-has-matching-output-type"; export function buildInputObjectDefinition( node: InputObjectTypeDefinitionNode, @@ -27,6 +28,11 @@ export function buildInputObjectDefinition( return ""; } + const typeWillBeConsolidated = inputTypeHasMatchingOutputType(node, schema); + if (typeWillBeConsolidated) { + return ""; + } + const classMembers = (node.fields ?? []) .map((arg) => { const typeToUse = buildTypeMetadata(arg.type, schema, config); diff --git a/src/helpers/input-type-has-matching-output-type.ts b/src/helpers/input-type-has-matching-output-type.ts new file mode 100644 index 0000000..590657e --- /dev/null +++ b/src/helpers/input-type-has-matching-output-type.ts @@ -0,0 +1,52 @@ +import { Kind, TypeNode } from "graphql/index"; +import { GraphQLSchema, InputObjectTypeDefinitionNode } from "graphql"; + +export function inputTypeHasMatchingOutputType( + inputTypeNode: InputObjectTypeDefinitionNode, + schema: GraphQLSchema, +) { + const typeNameWithoutInput = getTypeNameWithoutInput( + inputTypeNode.name.value, + ); + const matchingType = schema.getType(typeNameWithoutInput)?.astNode; + const matchingTypeFields = + matchingType?.kind === Kind.OBJECT_TYPE_DEFINITION + ? matchingType.fields + : []; + const inputFields = inputTypeNode.fields; + const fieldsMatch = matchingTypeFields?.every((field) => { + const matchingInputField = inputFields?.find( + (inputField) => inputField.name.value === field.name.value, + ); + if (!matchingInputField) return false; + return fieldsAreEquivalent(field.type, matchingInputField.type); + }); + return Boolean(matchingTypeFields?.length && fieldsMatch); +} + +function getTypeNameWithoutInput(name: string) { + return name.endsWith("Input") ? name.replace("Input", "") : name; +} + +function fieldsAreEquivalent( + typeField: TypeNode, + inputField: TypeNode, +): boolean { + switch (typeField.kind) { + case Kind.NAMED_TYPE: + return ( + inputField.kind === Kind.NAMED_TYPE && + typeField.name.value === getTypeNameWithoutInput(inputField.name.value) + ); + case Kind.LIST_TYPE: + return ( + inputField.kind === Kind.LIST_TYPE && + fieldsAreEquivalent(typeField.type, inputField.type) + ); + case Kind.NON_NULL_TYPE: + return ( + inputField.kind === Kind.NON_NULL_TYPE && + fieldsAreEquivalent(typeField.type, inputField.type) + ); + } +} diff --git a/test/unit/should_consolidate_input_and_output_types/expected.kt b/test/unit/should_consolidate_input_and_output_types/expected.kt new file mode 100644 index 0000000..5ad0a2a --- /dev/null +++ b/test/unit/should_consolidate_input_and_output_types/expected.kt @@ -0,0 +1,53 @@ +package com.kotlin.generated + +import com.expediagroup.graphql.generator.annotations.* + +data class MyTypeToConsolidate( + val field: List? = null, + val field2: NestedTypeToConsolidate? = null +) + +data class NestedTypeToConsolidate( + val field: String? = null +) + +@GraphQLDescription("A description for MyTypeToConsolidate2") +data class MyTypeToConsolidate2( + val field: String? = null +) + +data class MyTypeToConsolidate3( + val field: String? = null +) + +@GraphQLDescription("It always uses the type description when consolidating") +data class MyTypeToConsolidate4( + val field: String? = null +) + +data class MyTypeNotToConsolidate( + val field: String? = null +) + +@GraphQLDescription("The type name must exactly match in order to consolidate") +data class MyTypeToNotConsolidateInput( + val field: String? = null +) + +data class MyTypeToNotConsolidate2( + val field: String? = null +) + +data class MyTypeInputToNotConsolidate2( + val field: String? = null +) + +data class MyTypeWhereFieldsDoNotMatch( + val field: String? = null, + val field2: String? = null +) + +data class MyTypeWhereFieldsDoNotMatchInput( + val field: String? = null, + val field2: Int? = null +) diff --git a/test/unit/should_consolidate_input_and_output_types/schema.graphql b/test/unit/should_consolidate_input_and_output_types/schema.graphql new file mode 100644 index 0000000..2cacc6a --- /dev/null +++ b/test/unit/should_consolidate_input_and_output_types/schema.graphql @@ -0,0 +1,86 @@ +# typical case where input and output types are consolidated + +type MyTypeToConsolidate { + field: [String!] + field2: NestedTypeToConsolidate +} + +input MyTypeToConsolidateInput { + field: [String!] + field2: NestedTypeToConsolidateInput +} + +type NestedTypeToConsolidate { + field: String +} + +input NestedTypeToConsolidateInput { + field: String +} + +# case where type has description but input does not + +"A description for MyTypeToConsolidate2" +type MyTypeToConsolidate2 { + field: String +} + +input MyTypeToConsolidate2Input { + field: String +} + +# case where input has description but type does not + +type MyTypeToConsolidate3 { + field: String +} + +"It ignores the description on the input when consolidating" +input MyTypeToConsolidate3Input { + field: String +} + +# case where both type and input have descriptions + +"It always uses the type description when consolidating" +type MyTypeToConsolidate4 { + field: String +} + +"A description for MyTypeToConsolidateInput4" +input MyTypeToConsolidate4Input { + field: String +} + +# case where type and input names do not match + +type MyTypeNotToConsolidate { + field: String +} + +"The type name must exactly match in order to consolidate" +input MyTypeToNotConsolidateInput { + field: String +} + +# case where type has input in the middle of the name + +type MyTypeToNotConsolidate2 { + field: String +} + +input MyTypeInputToNotConsolidate2 { + field: String +} + +# case where type and input names match but fields do not match + +type MyTypeWhereFieldsDoNotMatch { + field: String + field2: String +} + +input MyTypeWhereFieldsDoNotMatchInput { + field: String + field2: Int +} diff --git a/test/unit/should_default_non-nullable_boolean_fields_to_false/expected.kt b/test/unit/should_default_non_nullable_boolean_fields_to_false/expected.kt similarity index 100% rename from test/unit/should_default_non-nullable_boolean_fields_to_false/expected.kt rename to test/unit/should_default_non_nullable_boolean_fields_to_false/expected.kt diff --git a/test/unit/should_default_non-nullable_boolean_fields_to_false/schema.graphql b/test/unit/should_default_non_nullable_boolean_fields_to_false/schema.graphql similarity index 100% rename from test/unit/should_default_non-nullable_boolean_fields_to_false/schema.graphql rename to test/unit/should_default_non_nullable_boolean_fields_to_false/schema.graphql diff --git a/test/unit/should_generate_multi-union_types_properly/expected.kt b/test/unit/should_generate_multi_union_types_properly/expected.kt similarity index 100% rename from test/unit/should_generate_multi-union_types_properly/expected.kt rename to test/unit/should_generate_multi_union_types_properly/expected.kt diff --git a/test/unit/should_generate_multi-union_types_properly/schema.graphql b/test/unit/should_generate_multi_union_types_properly/schema.graphql similarity index 100% rename from test/unit/should_generate_multi-union_types_properly/schema.graphql rename to test/unit/should_generate_multi_union_types_properly/schema.graphql 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 similarity index 100% rename from test/unit/should_generate_non-nullable_union_list_types_properly/expected.kt rename to test/unit/should_generate_non_nullable_union_list_types_properly/expected.kt diff --git a/test/unit/should_generate_non-nullable_union_list_types_properly/schema.graphql b/test/unit/should_generate_non_nullable_union_list_types_properly/schema.graphql similarity index 100% rename from test/unit/should_generate_non-nullable_union_list_types_properly/schema.graphql rename to test/unit/should_generate_non_nullable_union_list_types_properly/schema.graphql