Skip to content

Commit 5a8a18a

Browse files
committed
[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 493481f commit 5a8a18a

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
@@ -9,9 +9,7 @@ export type FooQueryVariables = Exact<{ [key: string]: never }>;
99

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

12-
export type LelFragment = { id: string; body: string | null; date: unknown | null } & {
13-
' $fragmentName'?: 'LelFragment';
14-
};
12+
export type LelFragment = { id: string; body: string | null; date: unknown } & { ' $fragmentName'?: 'LelFragment' };
1513

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

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

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

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

12-
export type LelFragment = { id: string; body: string | null; date: unknown | null } & {
13-
' $fragmentName'?: 'LelFragment';
14-
};
12+
export type LelFragment = { id: string; body: string | null; date: unknown } & { ' $fragmentName'?: 'LelFragment' };
1513

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

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,3 +741,25 @@ const getDeprecationReason = (directive: DirectiveNode): string | void => {
741741
return reason;
742742
}
743743
};
744+
745+
/**
746+
* @description Utility function to print a TypeScript type that is `Maybe`.
747+
* We need this since some TypeScript types have special handling.
748+
* e.g. `unknown | null | undefined` is treated as `unknown`
749+
*
750+
* Note: we currently have two types of handling nullable: `Maybe<T>` or `T | null | undefined`
751+
* This function only handles the latter case at the moment, but could be extended if needed.
752+
*
753+
* @param {Object} params
754+
* @param {string} params.type - The TypeScript type e.g. `any`, `unknown`, `string`, `Something`
755+
* @param {string} params.pattern - The pattern of the Maybe type. This is usually `T | null | undefined` or `T | null`
756+
* @returns {string} The TypeScript type as string
757+
*/
758+
export const printTypeScriptMaybeType = ({ type, pattern }: { type: string; pattern: string }): string => {
759+
if (type === 'any' || type === 'unknown') {
760+
return type;
761+
}
762+
763+
const nullableSuffix = pattern.replace('T', '');
764+
return type.endsWith(nullableSuffix) ? type : `${type}${nullableSuffix}`;
765+
};

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
@@ -5,6 +5,7 @@ import {
55
NormalizedScalarsMap,
66
OperationVariablesToObject,
77
ParsedEnumValuesMap,
8+
printTypeScriptMaybeType,
89
} from '@graphql-codegen/visitor-plugin-common';
910

1011
export const SCALARS = {
@@ -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,
@@ -62,7 +62,7 @@ export class TypeScriptOperationVariablesToObject extends OperationVariablesToOb
6262
}
6363

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

6767
if (str.endsWith(maybeSuffix)) {
6868
return (str = str.substring(0, str.length - maybeSuffix.length));
@@ -109,8 +109,9 @@ export class TypeScriptOperationVariablesToObject extends OperationVariablesToOb
109109
}
110110

111111
protected wrapMaybe(type: string): string {
112-
const maybeSuffix = this._config.inputMaybeValueSuffix;
113-
114-
return type.endsWith(maybeSuffix) ? type : `${type}${maybeSuffix}`;
112+
return printTypeScriptMaybeType({
113+
type,
114+
pattern: this._config.inputMaybeValue,
115+
});
115116
}
116117
}

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
ParsedDocumentsConfig,
4040
parseEnumValues,
4141
PreResolveTypesProcessor,
42+
printTypeScriptMaybeType,
4243
SelectionSetProcessorConfig,
4344
SelectionSetToObject,
4445
wrapTypeWithModifiers,
@@ -79,7 +80,6 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor<
7980
protected _usedNamedInputTypes: UsedNamedInputTypes = {};
8081
protected _needsExactUtilityType: boolean = false;
8182
private _outputPath: string;
82-
private _inputMaybeValueSuffix: string;
8383

8484
constructor(
8585
schema: GraphQLSchema,
@@ -141,7 +141,11 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor<
141141
},
142142
wrapTypeWithModifiers: (baseType, type) => {
143143
return wrapTypeWithModifiers(baseType, type, {
144-
wrapOptional: type => this.config.maybeValue.replace('T', type),
144+
wrapOptional: type =>
145+
printTypeScriptMaybeType({
146+
type,
147+
pattern: this.config.maybeValue,
148+
}),
145149
wrapArray: type => {
146150
const listModifier = this.config.immutableTypes ? 'ReadonlyArray' : 'Array';
147151
return `${listModifier}<${type}>`;
@@ -163,8 +167,6 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor<
163167
),
164168
);
165169

166-
this._inputMaybeValueSuffix = this.config.inputMaybeValue.replace('T', ''); // e.g. turns `T | null | undefined` to ` | null | undefined`
167-
168170
const enumsNames = Object.keys(schema.getTypeMap()).filter(typeName =>
169171
isEnumType(schema.getType(typeName)),
170172
);
@@ -177,7 +179,6 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor<
177179
avoidOptionals: this.config.avoidOptionals,
178180
immutableTypes: this.config.immutableTypes,
179181
inputMaybeValue: this.config.inputMaybeValue,
180-
inputMaybeValueSuffix: this._inputMaybeValueSuffix,
181182
},
182183
this.scalars,
183184
this.convertName.bind(this),
@@ -284,15 +285,21 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor<
284285

285286
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
286287
if (!typeNode.isNonNullable) {
287-
typePart += this._inputMaybeValueSuffix;
288+
typePart = printTypeScriptMaybeType({
289+
type: typePart,
290+
pattern: this.config.inputMaybeValue,
291+
});
288292
}
289293
continue;
290294
}
291295

292296
if (typeNode.type === 'ListType') {
293297
typePart = `Array<${typePart}>`;
294298
if (!typeNode.isNonNullable) {
295-
typePart += this._inputMaybeValueSuffix;
299+
typePart = printTypeScriptMaybeType({
300+
type: typePart,
301+
pattern: this.config.inputMaybeValue,
302+
});
296303
}
297304
}
298305
}

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
@@ -2904,7 +2904,7 @@ export type Q2Query = { search: Array<
29042904
expect(content).toMatchInlineSnapshot(
29052905
`
29062906
"export type TestQueryQueryVariables = Exact<{
2907-
test?: unknown | null | undefined;
2907+
test?: unknown;
29082908
}>;
29092909
29102910

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)