Skip to content

Commit 43b525d

Browse files
authored
fix(visitor-plugin-common): avoid reading from null values (#9709)
* fix(visitor-plugin-common): avoid reading from null values * Add e2e test
1 parent 5d37ab6 commit 43b525d

File tree

6 files changed

+116
-1
lines changed

6 files changed

+116
-1
lines changed

.changeset/perfect-forks-flash.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-codegen/visitor-plugin-common': patch
3+
---
4+
5+
Avoid reading from null values when selection sets only contain fragments.

dev-test/codegen.ts

+11
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,17 @@ const config: CodegenConfig = {
221221
preset: 'client',
222222
presetConfig: { fragmentMasking: true },
223223
},
224+
'./dev-test/test-null-value/result.d.ts': {
225+
schema: './dev-test/test-null-value/schema.graphql',
226+
documents: ['./dev-test/test-null-value/query.ts'],
227+
plugins: ['typescript', 'typescript-operations'],
228+
config: {
229+
// The combination of these two flags caused the following issue:
230+
// https://github.com/dotansimha/graphql-code-generator/pull/9709
231+
skipTypename: true,
232+
mergeFragmentTypes: true,
233+
},
234+
},
224235
},
225236
};
226237

dev-test/test-null-value/query.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export const MY_QUERY = /* GraphQL */ `
2+
fragment CartLine on CartLine {
3+
id
4+
quantity
5+
}
6+
7+
query Test {
8+
cart {
9+
lines {
10+
nodes {
11+
...CartLine
12+
}
13+
}
14+
}
15+
}
16+
`;

dev-test/test-null-value/result.d.ts

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
export type Maybe<T> = T | null;
2+
export type InputMaybe<T> = Maybe<T>;
3+
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
4+
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
5+
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
6+
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
7+
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
8+
/** All built-in and custom scalars, mapped to their actual values */
9+
export type Scalars = {
10+
ID: { input: string; output: string };
11+
String: { input: string; output: string };
12+
Boolean: { input: boolean; output: boolean };
13+
Int: { input: number; output: number };
14+
Float: { input: number; output: number };
15+
};
16+
17+
export type BaseCartLine = {
18+
id: Scalars['String']['output'];
19+
quantity: Scalars['Int']['output'];
20+
};
21+
22+
export type BaseCartLineConnection = {
23+
id: Scalars['String']['output'];
24+
nodes: Array<BaseCartLine>;
25+
};
26+
27+
export type Cart = {
28+
id: Scalars['String']['output'];
29+
lines: BaseCartLineConnection;
30+
};
31+
32+
export type CartLine = BaseCartLine & {
33+
id: Scalars['String']['output'];
34+
quantity: Scalars['Int']['output'];
35+
};
36+
37+
export type ComponentizableCartLine = BaseCartLine & {
38+
id: Scalars['String']['output'];
39+
quantity: Scalars['Int']['output'];
40+
};
41+
42+
export type QueryRoot = {
43+
cart?: Maybe<Cart>;
44+
};
45+
46+
export type CartLineFragment = { id: string; quantity: number };
47+
48+
export type TestQueryVariables = Exact<{ [key: string]: never }>;
49+
50+
export type TestQuery = { cart?: { lines: { nodes: Array<{ id: string; quantity: number }> } } | null };
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
schema {
2+
query: QueryRoot
3+
}
4+
5+
interface BaseCartLine {
6+
id: String!
7+
quantity: Int!
8+
}
9+
10+
type BaseCartLineConnection {
11+
id: String!
12+
nodes: [BaseCartLine!]!
13+
}
14+
15+
type Cart {
16+
id: String!
17+
lines: BaseCartLineConnection!
18+
}
19+
20+
type CartLine implements BaseCartLine {
21+
id: String!
22+
quantity: Int!
23+
}
24+
25+
type ComponentizableCartLine implements BaseCartLine {
26+
id: String!
27+
quantity: Int!
28+
}
29+
30+
type QueryRoot {
31+
cart: Cart
32+
}

packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,6 @@ export class SelectionSetToObject<Config extends ParsedDocumentsConfig = ParsedD
714714
return (
715715
Object.keys(grouped)
716716
.map(typeName => {
717-
const hasUnions = grouped[typeName].filter(s => typeof s !== 'string' && s.union).length > 0;
718717
const relevant = grouped[typeName].filter(Boolean);
719718

720719
if (relevant.length === 0) {
@@ -729,6 +728,8 @@ export class SelectionSetToObject<Config extends ParsedDocumentsConfig = ParsedD
729728
})
730729
.join(' & ');
731730

731+
const hasUnions = grouped[typeName].filter(s => typeof s !== 'string' && s.union).length > 0;
732+
732733
return relevant.length > 1 && !hasUnions ? `( ${res} )` : res;
733734
})
734735
.filter(Boolean)

0 commit comments

Comments
 (0)