Skip to content

Commit 93ace19

Browse files
authored
[typescript-operations] Handle printing "Maybe" types consistently when types are any and unknown (#10574)
* Use printTypeScriptType to handle printing typescript types consistently, especially for any and unknown * Update tests * Add changeset * Make printTypeScriptMaybeType scope smaller * Update tests
1 parent b55828d commit 93ace19

12 files changed

Lines changed: 87 additions & 55 deletions

File tree

.changeset/free-fans-dance.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@graphql-codegen/visitor-plugin-common': patch
3+
'@graphql-codegen/typescript-operations': patch
4+
---
5+
6+
Add `printTypeScriptMaybeType` to handle printing TS types, as there are special cases like `any` and `unknown`

dev-test/gql-tag-operations/gql/graphql.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ export type FooQueryVariables = Exact<{ [key: string]: never }>;
66

77
export type FooQuery = { Tweets: Array<{ id: string } | null> | null };
88

9-
export type LelFragment = { id: string; body: string | null; date: unknown | null } & {
10-
' $fragmentName'?: 'LelFragment';
11-
};
9+
export type LelFragment = { id: string; body: string | null; date: unknown } & { ' $fragmentName'?: 'LelFragment' };
1210

1311
export type BarQueryVariables = Exact<{ [key: string]: never }>;
1412

dev-test/gql-tag-operations/graphql/graphql.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ export type FooQueryVariables = Exact<{ [key: string]: never }>;
66

77
export type FooQuery = { Tweets: Array<{ id: string } | null> | null };
88

9-
export type LelFragment = { id: string; body: string | null; date: unknown | null } & {
10-
' $fragmentName'?: 'LelFragment';
11-
};
9+
export type LelFragment = { id: string; body: string | null; date: unknown } & { ' $fragmentName'?: 'LelFragment' };
1210

1311
export type BarQueryVariables = Exact<{ [key: string]: never }>;
1412

packages/plugins/other/visitor-plugin-common/src/utils.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,3 +687,25 @@ const getDeprecationReason = (directive: DirectiveNode): string | void => {
687687
return reason;
688688
}
689689
};
690+
691+
/**
692+
* @description Utility function to print a TypeScript type that is `Maybe`.
693+
* We need this since some TypeScript types have special handling.
694+
* e.g. `unknown | null | undefined` is treated as `unknown`
695+
*
696+
* Note: we currently have two types of handling nullable: `Maybe<T>` or `T | null | undefined`
697+
* This function only handles the latter case at the moment, but could be extended if needed.
698+
*
699+
* @param {Object} params
700+
* @param {string} params.type - The TypeScript type e.g. `any`, `unknown`, `string`, `Something`
701+
* @param {string} params.pattern - The pattern of the Maybe type. This is usually `T | null | undefined` or `T | null`
702+
* @returns {string} The TypeScript type as string
703+
*/
704+
export const printTypeScriptMaybeType = ({ type, pattern }: { type: string; pattern: string }): string => {
705+
if (type === 'any' || type === 'unknown') {
706+
return type;
707+
}
708+
709+
const nullableSuffix = pattern.replace('T', '');
710+
return type.endsWith(nullableSuffix) ? type : `${type}${nullableSuffix}`;
711+
};

packages/plugins/typescript/operations/src/ts-operation-variables-to-object.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
NormalizedAvoidOptionalsConfig,
55
NormalizedScalarsMap,
66
ParsedEnumValuesMap,
7+
printTypeScriptMaybeType,
78
} from '@graphql-codegen/visitor-plugin-common';
89
import { Kind, TypeNode } from 'graphql';
910

@@ -21,7 +22,6 @@ export class TypeScriptOperationVariablesToObject extends OperationVariablesToOb
2122
avoidOptionals: NormalizedAvoidOptionalsConfig;
2223
immutableTypes: boolean;
2324
inputMaybeValue: string;
24-
inputMaybeValueSuffix: string;
2525
},
2626
_scalars: NormalizedScalarsMap,
2727
_convertName: ConvertNameFn,
@@ -54,7 +54,7 @@ export class TypeScriptOperationVariablesToObject extends OperationVariablesToOb
5454
}
5555

5656
protected clearOptional(str: string): string {
57-
const maybeSuffix = this._config.inputMaybeValueSuffix;
57+
const maybeSuffix = this._config.inputMaybeValue.replace('T', ''); // e.g. turns `T | null | undefined` to ` | null | undefined`
5858

5959
if (str.endsWith(maybeSuffix)) {
6060
return (str = str.substring(0, str.length - maybeSuffix.length));
@@ -94,8 +94,9 @@ export class TypeScriptOperationVariablesToObject extends OperationVariablesToOb
9494
}
9595

9696
protected wrapMaybe(type: string): string {
97-
const maybeSuffix = this._config.inputMaybeValueSuffix;
98-
99-
return type.endsWith(maybeSuffix) ? type : `${type}${maybeSuffix}`;
97+
return printTypeScriptMaybeType({
98+
type,
99+
pattern: this._config.inputMaybeValue,
100+
});
100101
}
101102
}

packages/plugins/typescript/operations/src/visitor.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
SelectionSetToObject,
2323
getNodeComment,
2424
wrapTypeWithModifiers,
25+
printTypeScriptMaybeType,
2526
} from '@graphql-codegen/visitor-plugin-common';
2627
import { normalizeImportExtension } from '@graphql-codegen/plugin-helpers';
2728
import autoBind from 'auto-bind';
@@ -76,7 +77,6 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor<
7677
protected _usedNamedInputTypes: UsedNamedInputTypes = {};
7778
protected _needsExactUtilityType: boolean = false;
7879
private _outputPath: string;
79-
private _inputMaybeValueSuffix: string;
8080

8181
constructor(
8282
schema: GraphQLSchema,
@@ -136,7 +136,11 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor<
136136
},
137137
wrapTypeWithModifiers: (baseType, type) => {
138138
return wrapTypeWithModifiers(baseType, type, {
139-
wrapOptional: type => this.config.maybeValue.replace('T', type),
139+
wrapOptional: type =>
140+
printTypeScriptMaybeType({
141+
type,
142+
pattern: this.config.maybeValue,
143+
}),
140144
wrapArray: type => {
141145
const listModifier = this.config.immutableTypes ? 'ReadonlyArray' : 'Array';
142146
return `${listModifier}<${type}>`;
@@ -158,8 +162,6 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor<
158162
)
159163
);
160164

161-
this._inputMaybeValueSuffix = this.config.inputMaybeValue.replace('T', ''); // e.g. turns `T | null | undefined` to ` | null | undefined`
162-
163165
const enumsNames = Object.keys(schema.getTypeMap()).filter(typeName => isEnumType(schema.getType(typeName)));
164166
this.setVariablesTransformer(
165167
new TypeScriptOperationVariablesToObject(
@@ -170,7 +172,6 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor<
170172
avoidOptionals: this.config.avoidOptionals,
171173
immutableTypes: this.config.immutableTypes,
172174
inputMaybeValue: this.config.inputMaybeValue,
173-
inputMaybeValueSuffix: this._inputMaybeValueSuffix,
174175
},
175176
this.scalars,
176177
this.convertName.bind(this),
@@ -277,15 +278,21 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor<
277278

278279
typePart = usedInputType.tsType; // If the schema is correct, when reversing typeNodes, the first node would be `NamedType`, which means we can safely set it as the base for typePart
279280
if (!typeNode.isNonNullable) {
280-
typePart += this._inputMaybeValueSuffix;
281+
typePart = printTypeScriptMaybeType({
282+
type: typePart,
283+
pattern: this.config.inputMaybeValue,
284+
});
281285
}
282286
continue;
283287
}
284288

285289
if (typeNode.type === 'ListType') {
286290
typePart = `Array<${typePart}>`;
287291
if (!typeNode.isNonNullable) {
288-
typePart += this._inputMaybeValueSuffix;
292+
typePart = printTypeScriptMaybeType({
293+
type: typePart,
294+
pattern: this.config.inputMaybeValue,
295+
});
289296
}
290297
}
291298
}

packages/plugins/typescript/operations/tests/__snapshots__/ts-documents.spec.ts.snap

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,9 @@ export type AaaQuery = { user:
155155
`;
156156

157157
exports[`TypeScript Operations Plugin > Selection Set > Should have valid __typename usage and split types according to that (with usage) 1`] = `
158-
"type NetRoute_Ipv4Route_Fragment = { __typename: 'IPV4Route', ipv4Address: unknown | null, ipv4Gateway: unknown | null };
158+
"type NetRoute_Ipv4Route_Fragment = { __typename: 'IPV4Route', ipv4Address: unknown, ipv4Gateway: unknown };
159159
160-
type NetRoute_Ipv6Route_Fragment = { __typename: 'IPV6Route', ipv6Address: unknown | null, ipv6Gateway: unknown | null };
160+
type NetRoute_Ipv6Route_Fragment = { __typename: 'IPV6Route', ipv6Address: unknown, ipv6Gateway: unknown };
161161
162162
export type NetRouteFragment =
163163
| NetRoute_Ipv4Route_Fragment
@@ -168,30 +168,30 @@ export type QqQueryVariables = Exact<{ [key: string]: never; }>;
168168
169169
170170
export type QqQuery = { routes: Array<
171-
| { __typename: 'IPV4Route', ipv4Address: unknown | null, ipv4Gateway: unknown | null }
172-
| { __typename: 'IPV6Route', ipv6Address: unknown | null, ipv6Gateway: unknown | null }
171+
| { __typename: 'IPV4Route', ipv4Address: unknown, ipv4Gateway: unknown }
172+
| { __typename: 'IPV6Route', ipv6Address: unknown, ipv6Gateway: unknown }
173173
> };
174174
"
175175
`;
176176

177177
exports[`TypeScript Operations Plugin > Selection Set > Should have valid __typename usage and split types according to that (with usage) 2`] = `
178-
"type NetRoute_Ipv4Route_Fragment = { __typename: 'IPV4Route', ipv4Address: unknown | null, ipv4Gateway: unknown | null };
178+
"type NetRoute_Ipv4Route_Fragment = { __typename: 'IPV4Route', ipv4Address: unknown, ipv4Gateway: unknown };
179179
180-
type NetRoute_Ipv6Route_Fragment = { __typename: 'IPV6Route', ipv6Address: unknown | null, ipv6Gateway: unknown | null };
180+
type NetRoute_Ipv6Route_Fragment = { __typename: 'IPV6Route', ipv6Address: unknown, ipv6Gateway: unknown };
181181
182182
export type NetRouteFragment =
183183
| NetRoute_Ipv4Route_Fragment
184184
| NetRoute_Ipv6Route_Fragment
185185
;
186186
187-
export type TestFragment = { ipv6Address: unknown | null, ipv6Gateway: unknown | null };
187+
export type TestFragment = { ipv6Address: unknown, ipv6Gateway: unknown };
188188
189189
export type QqQueryVariables = Exact<{ [key: string]: never; }>;
190190
191191
192192
export type QqQuery = { routes: Array<
193-
| { __typename: 'IPV4Route', ipv4Address: unknown | null, ipv4Gateway: unknown | null }
194-
| { __typename: 'IPV6Route', ipv6Address: unknown | null, ipv6Gateway: unknown | null }
193+
| { __typename: 'IPV4Route', ipv4Address: unknown, ipv4Gateway: unknown }
194+
| { __typename: 'IPV6Route', ipv6Address: unknown, ipv6Gateway: unknown }
195195
> };
196196
"
197197
`;

packages/plugins/typescript/operations/tests/ts-documents.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2749,7 +2749,7 @@ export type Q2Query = { search: Array<
27492749
expect(content).toMatchInlineSnapshot(
27502750
`
27512751
"export type TestQueryQueryVariables = Exact<{
2752-
test?: unknown | null | undefined;
2752+
test?: unknown;
27532753
}>;
27542754
27552755

packages/plugins/typescript/operations/tests/ts-documents.standalone.default-scalar-types.spec.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -70,25 +70,25 @@ describe('TypeScript Operations Plugin - Default Scalar types', () => {
7070
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
7171
type UserInput = {
7272
nonNullableDate: unknown;
73-
nullableDate?: unknown | null | undefined;
74-
dateArray1?: Array<unknown | null | undefined> | null | undefined;
75-
dateArray2: Array<unknown | null | undefined>;
73+
nullableDate?: unknown;
74+
dateArray1?: Array<unknown> | null | undefined;
75+
dateArray2: Array<unknown>;
7676
dateArray3?: Array<unknown> | null | undefined;
7777
dateArray4: Array<unknown>;
7878
};
7979
8080
export type UserQueryVariables = Exact<{
8181
nonNullableDate: unknown;
82-
nullableDate?: unknown | null | undefined;
83-
dateArray1?: Array<unknown | null | undefined> | unknown | null | undefined;
84-
dateArray2: Array<unknown | null | undefined> | unknown;
82+
nullableDate?: unknown;
83+
dateArray1?: Array<unknown> | unknown | null | undefined;
84+
dateArray2: Array<unknown> | unknown;
8585
dateArray3?: Array<unknown> | unknown | null | undefined;
8686
dateArray4: Array<unknown> | unknown;
8787
input: UserInput;
8888
}>;
8989
9090
91-
export type UserQuery = { user: { id: string, nonNullableDate: unknown, nullableDate: unknown | null } | null };
91+
export type UserQuery = { user: { id: string, nonNullableDate: unknown, nullableDate: unknown } | null };
9292
"
9393
`);
9494

@@ -163,25 +163,25 @@ describe('TypeScript Operations Plugin - Default Scalar types', () => {
163163
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
164164
type UserInput = {
165165
nonNullableDate: any;
166-
nullableDate?: any | null | undefined;
167-
dateArray1?: Array<any | null | undefined> | null | undefined;
168-
dateArray2: Array<any | null | undefined>;
166+
nullableDate?: any;
167+
dateArray1?: Array<any> | null | undefined;
168+
dateArray2: Array<any>;
169169
dateArray3?: Array<any> | null | undefined;
170170
dateArray4: Array<any>;
171171
};
172172
173173
export type UserQueryVariables = Exact<{
174174
nonNullableDate: any;
175-
nullableDate?: any | null | undefined;
176-
dateArray1?: Array<any | null | undefined> | any | null | undefined;
177-
dateArray2: Array<any | null | undefined> | any;
175+
nullableDate?: any;
176+
dateArray1?: Array<any> | any | null | undefined;
177+
dateArray2: Array<any> | any;
178178
dateArray3?: Array<any> | any | null | undefined;
179179
dateArray4: Array<any> | any;
180180
input: UserInput;
181181
}>;
182182
183183
184-
export type UserQuery = { user: { id: string, nonNullableDate: any, nullableDate: any | null } | null };
184+
export type UserQuery = { user: { id: string, nonNullableDate: any, nullableDate: any } | null };
185185
"
186186
`);
187187

packages/plugins/typescript/operations/tests/ts-documents.standalone.import-types.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,9 @@ describe('TypeScript Operations Plugin - Import Types', () => {
110110
/** UsersInput Description */
111111
type UsersInput = {
112112
/** UsersInput from */
113-
from?: unknown | null | undefined;
113+
from?: unknown;
114114
/** UsersInput to */
115-
to?: unknown | null | undefined;
115+
to?: unknown;
116116
role?: UserRole | null | undefined;
117117
};
118118
@@ -135,7 +135,7 @@ describe('TypeScript Operations Plugin - Import Types', () => {
135135
136136
export type UsersWithScalarInputQueryVariables = Exact<{
137137
from: unknown;
138-
to?: unknown | null | undefined;
138+
to?: unknown;
139139
role?: TypeImport.UserRole | null | undefined;
140140
}>;
141141
@@ -254,9 +254,9 @@ describe('TypeScript Operations Plugin - Import Types', () => {
254254
/** UsersInput Description */
255255
type UsersInput = {
256256
/** UsersInput from */
257-
from?: unknown | null | undefined;
257+
from?: unknown;
258258
/** UsersInput to */
259-
to?: unknown | null | undefined;
259+
to?: unknown;
260260
role?: UserRole | null | undefined;
261261
};
262262
@@ -279,7 +279,7 @@ describe('TypeScript Operations Plugin - Import Types', () => {
279279
280280
export type UsersWithScalarInputQueryVariables = Exact<{
281281
from: unknown;
282-
to?: unknown | null | undefined;
282+
to?: unknown;
283283
role?: TypeImport.UserRole | null | undefined;
284284
}>;
285285

0 commit comments

Comments
 (0)