diff --git a/.changeset/perfect-forks-flash.md b/.changeset/perfect-forks-flash.md new file mode 100644 index 00000000000..c9a037331d4 --- /dev/null +++ b/.changeset/perfect-forks-flash.md @@ -0,0 +1,5 @@ +--- +'@graphql-codegen/visitor-plugin-common': patch +--- + +Avoid reading from null values when selection sets only contain fragments. diff --git a/dev-test/codegen.ts b/dev-test/codegen.ts index fb2419cd2a2..ba8afd46511 100644 --- a/dev-test/codegen.ts +++ b/dev-test/codegen.ts @@ -221,6 +221,17 @@ const config: CodegenConfig = { preset: 'client', presetConfig: { fragmentMasking: true }, }, + './dev-test/test-null-value/result.d.ts': { + schema: './dev-test/test-null-value/schema.graphql', + documents: ['./dev-test/test-null-value/query.ts'], + plugins: ['typescript', 'typescript-operations'], + config: { + // The combination of these two flags caused the following issue: + // https://github.com/dotansimha/graphql-code-generator/pull/9709 + skipTypename: true, + mergeFragmentTypes: true, + }, + }, }, }; diff --git a/dev-test/test-null-value/query.ts b/dev-test/test-null-value/query.ts new file mode 100644 index 00000000000..c270b53c57a --- /dev/null +++ b/dev-test/test-null-value/query.ts @@ -0,0 +1,16 @@ +export const MY_QUERY = /* GraphQL */ ` + fragment CartLine on CartLine { + id + quantity + } + + query Test { + cart { + lines { + nodes { + ...CartLine + } + } + } + } +`; diff --git a/dev-test/test-null-value/result.d.ts b/dev-test/test-null-value/result.d.ts new file mode 100644 index 00000000000..9e88d265c27 --- /dev/null +++ b/dev-test/test-null-value/result.d.ts @@ -0,0 +1,50 @@ +export type Maybe = T | null; +export type InputMaybe = Maybe; +export type Exact = { [K in keyof T]: T[K] }; +export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; +export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; +export type MakeEmpty = { [_ in K]?: never }; +export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: { input: string; output: string }; + String: { input: string; output: string }; + Boolean: { input: boolean; output: boolean }; + Int: { input: number; output: number }; + Float: { input: number; output: number }; +}; + +export type BaseCartLine = { + id: Scalars['String']['output']; + quantity: Scalars['Int']['output']; +}; + +export type BaseCartLineConnection = { + id: Scalars['String']['output']; + nodes: Array; +}; + +export type Cart = { + id: Scalars['String']['output']; + lines: BaseCartLineConnection; +}; + +export type CartLine = BaseCartLine & { + id: Scalars['String']['output']; + quantity: Scalars['Int']['output']; +}; + +export type ComponentizableCartLine = BaseCartLine & { + id: Scalars['String']['output']; + quantity: Scalars['Int']['output']; +}; + +export type QueryRoot = { + cart?: Maybe; +}; + +export type CartLineFragment = { id: string; quantity: number }; + +export type TestQueryVariables = Exact<{ [key: string]: never }>; + +export type TestQuery = { cart?: { lines: { nodes: Array<{ id: string; quantity: number }> } } | null }; diff --git a/dev-test/test-null-value/schema.graphql b/dev-test/test-null-value/schema.graphql new file mode 100644 index 00000000000..3b43e75237e --- /dev/null +++ b/dev-test/test-null-value/schema.graphql @@ -0,0 +1,32 @@ +schema { + query: QueryRoot +} + +interface BaseCartLine { + id: String! + quantity: Int! +} + +type BaseCartLineConnection { + id: String! + nodes: [BaseCartLine!]! +} + +type Cart { + id: String! + lines: BaseCartLineConnection! +} + +type CartLine implements BaseCartLine { + id: String! + quantity: Int! +} + +type ComponentizableCartLine implements BaseCartLine { + id: String! + quantity: Int! +} + +type QueryRoot { + cart: Cart +} diff --git a/packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts b/packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts index c5dd7c4ad9b..fb16ad0bc6b 100644 --- a/packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts +++ b/packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts @@ -714,7 +714,6 @@ export class SelectionSetToObject { - const hasUnions = grouped[typeName].filter(s => typeof s !== 'string' && s.union).length > 0; const relevant = grouped[typeName].filter(Boolean); if (relevant.length === 0) { @@ -729,6 +728,8 @@ export class SelectionSetToObject typeof s !== 'string' && s.union).length > 0; + return relevant.length > 1 && !hasUnions ? `( ${res} )` : res; }) .filter(Boolean)