Skip to content

Commit

Permalink
feat(typescript-operations): add arrayInputCoercion option (#6007)
Browse files Browse the repository at this point in the history
This adds the arrayInputCoercion option to prohibit input type
coercion in the generated TypeScript definition.
  • Loading branch information
BridgeAR committed May 18, 2021
1 parent d8ee188 commit 0a90986
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 23 deletions.
5 changes: 5 additions & 0 deletions .changeset/shiny-steaks-sparkle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-codegen/typescript-operations': minor
---

Add arrayInputCoercion option
21 changes: 20 additions & 1 deletion packages/plugins/typescript/operations/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@ import { RawDocumentsConfig, AvoidOptionalsConfig } from '@graphql-codegen/visit
* Note: In most configurations, this plugin requires you to use `typescript as well, because it depends on its base types.
*/
export interface TypeScriptDocumentsPluginConfig extends RawDocumentsConfig {
/**
* @description The [GraphQL spec]{@link https://spec.graphql.org/draft/#sel-FAHjBJFCAACE_Gh7d}
* allows arrays and a single primitive value for list input. This allows to
* deactivate that behavior to only accept arrays instead of single values. If
* set to `false`, the definition: `query foo(bar: [Int!]!): Foo` will output
* `bar: Array<Int>` instead of `bar: Array<Int> | Int` for the variable part.
* @default true
*
* @exampleMarkdown
* ```yml
* generates:
* path/to/file.ts:
* plugins:
* - typescript
* config:
* arrayInputCoercion: false
* ```
*/
arrayInputCoercion?: boolean;
/**
* @description This will cause the generator to avoid using TypeScript optionals (`?`) on types,
* so the following definition: `type A { myField: String }` will output `myField: Maybe<string>`
Expand Down Expand Up @@ -73,7 +92,7 @@ export interface TypeScriptDocumentsPluginConfig extends RawDocumentsConfig {
*/
flattenGeneratedTypes?: boolean;
/**
* @description Set the to `true` in order to generate output without `export` modifier.
* @description Set to `true` in order to generate output without `export` modifier.
* This is useful if you are generating `.d.ts` file and want it to be globally available.
* @default false
*
Expand Down
4 changes: 3 additions & 1 deletion packages/plugins/typescript/operations/src/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { TypeScriptOperationVariablesToObject } from './ts-operation-variables-t
import { TypeScriptSelectionSetProcessor } from './ts-selection-set-processor';

export interface TypeScriptDocumentsParsedConfig extends ParsedDocumentsConfig {
arrayInputCoercion: boolean;
avoidOptionals: AvoidOptionalsConfig;
immutableTypes: boolean;
noExport: boolean;
Expand All @@ -32,6 +33,7 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor<
super(
config,
{
arrayInputCoercion: getConfigValue(config.arrayInputCoercion, true),
noExport: getConfigValue(config.noExport, false),
avoidOptionals: normalizeAvoidOptionals(getConfigValue(config.avoidOptionals, false)),
immutableTypes: getConfigValue(config.immutableTypes, false),
Expand Down Expand Up @@ -96,7 +98,7 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor<
enumsNames,
this.config.enumPrefix,
this.config.enumValues,
true
this.config.arrayInputCoercion
)
);
this._declarationBlockConfig = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ function test(q: QQuery) {
if (q.hotel) {
const t1 = q.hotel.gpsPosition.lat
}
if (q.transport) {
const t2 = q.transport.id;
}
Expand Down
72 changes: 52 additions & 20 deletions packages/plugins/typescript/operations/tests/ts-documents.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1099,7 +1099,7 @@ describe('TypeScript Operations Plugin', () => {
});
expect(content).toBeSimilarStringTo(`
export type UnionTestQuery = (
{ __typename?: 'Query' }
{ __typename?: 'Query' }
& { unionTest?: Maybe<(
{ __typename?: 'User' }
& Pick<User, 'id'>
Expand Down Expand Up @@ -1908,12 +1908,12 @@ describe('TypeScript Operations Plugin', () => {
& BFragment
) | { __typename?: 'B' }> }
);
export type AFragment = (
{ __typename?: 'A' }
& Pick<A, 'id'>
);
export type BFragment = (
{ __typename?: 'A' }
& Pick<A, 'x'>
Expand Down Expand Up @@ -3195,7 +3195,7 @@ describe('TypeScript Operations Plugin', () => {

expect(content).toBeSimilarStringTo(`
export type UserQuery = (
{ __typename?: 'Query' }
{ __typename?: 'Query' }
& { user?: Maybe<(
{ __typename?: 'User' }
& Pick<User, 'id' | 'login'>
Expand Down Expand Up @@ -3453,36 +3453,36 @@ describe('TypeScript Operations Plugin', () => {
{ __typename?: 'AdditionalInfo' }
& Pick<AdditionalInfo, 'message'>
);
type UserResult1_User_Fragment = (
{ __typename?: 'User' }
& Pick<User, 'id'>
);
type UserResult1_Error2_Fragment = { __typename?: 'Error2' };
type UserResult1_Error3_Fragment = (
{ __typename?: 'Error3' }
& { info?: Maybe<(
{ __typename?: 'AdditionalInfo' }
& Pick<AdditionalInfo, 'message2'>
)> }
);
export type UserResult1Fragment = UserResult1_User_Fragment | UserResult1_Error2_Fragment | UserResult1_Error3_Fragment;
type UserResult_User_Fragment = (
{ __typename?: 'User' }
& Pick<User, 'id'>
);
type UserResult_Error2_Fragment = (
{ __typename?: 'Error2' }
& Pick<Error2, 'message'>
);
type UserResult_Error3_Fragment = { __typename?: 'Error3' };
export type UserResultFragment = UserResult_User_Fragment | UserResult_Error2_Fragment | UserResult_Error3_Fragment;`);
});

Expand Down Expand Up @@ -3771,7 +3771,7 @@ describe('TypeScript Operations Plugin', () => {
& Pick<User, 'id'>
) }
);
export type UserLoginQueryQueryVariables = Exact<{ [key: string]: never; }>;
export type UserLoginQueryQuery = (
Expand All @@ -3781,7 +3781,7 @@ describe('TypeScript Operations Plugin', () => {
& Pick<User, 'login'>
) }
);
export declare const UserIdQuery: import("graphql").DocumentNode;
export declare const UserLoginQuery: import("graphql").DocumentNode;
`);
Expand Down Expand Up @@ -4170,7 +4170,7 @@ describe('TypeScript Operations Plugin', () => {
if (q.hotel) {
const t1 = q.hotel.gpsPosition.lat
}
if (q.transport) {
const t2 = q.transport.id;
}
Expand Down Expand Up @@ -4778,6 +4778,38 @@ function test(q: GetEntityBrandDataQuery): void {
await validate(content, config);
});

it('#5352 - Prevent array input coercion if arrayInputCoercion = false', async () => {
const schema = buildSchema(/* GraphQL */ `
type User {
id: ID!
}
type Query {
search(testArray: [String], requireString: [String]!, innerRequired: [String!]!): [User!]
}
`);

const ast = parse(/* GraphQL */ `
query user($testArray: [String], $requireString: [String]!, $innerRequired: [String!]!) {
search(testArray: $testArray, requireString: $requireString, innerRequired: $innerRequired) {
id
}
}
`);
const config = { preResolveTypes: true, arrayInputCoercion: false };
const { content } = await plugin(schema, [{ location: '', document: ast }], config, {
outputFile: 'graphql.ts',
});

expect(content).toBeSimilarStringTo(`
export type UserQueryVariables = Exact<{
testArray?: Maybe<Array<Maybe<Scalars['String']>>>;
requireString: Array<Maybe<Scalars['String']>>;
innerRequired: Array<Scalars['String']>;
}>;`);
await validate(content, config);
});

it('#5263 - inline fragment spread on interface field results in incorrect types', async () => {
const schema = buildSchema(/* GraphQL */ `
interface Entity {
Expand Down Expand Up @@ -4877,7 +4909,7 @@ function test(q: GetEntityBrandDataQuery): void {
showAddress: Scalars['Boolean'];
}>;
export type UserQuery = { __typename?: 'Query', user: { __typename?: 'User', name: string, address?: Maybe<string> } };`);
});

Expand Down Expand Up @@ -4979,13 +5011,13 @@ function test(q: GetEntityBrandDataQuery): void {
showAddress: Scalars['Boolean'];
showName: Scalars['Boolean'];
}>;
export type UserQuery = (
{ __typename?: 'Query' }
& { user: (
{ __typename?: 'User' }
& MakeOptional<Pick<User, 'id' | 'name'>, 'name'>
& MakeOptional<Pick<User, 'id' | 'name'>, 'name'>
& { address?: Maybe<(
{ __typename?: 'Address' }
& Pick<Address, 'city'>
Expand Down Expand Up @@ -5033,7 +5065,7 @@ function test(q: GetEntityBrandDataQuery): void {

expect(content).toBeSimilarStringTo(`
export type UserQueryVariables = Exact<{ [key: string]: never; }>;
export type UserQuery = (
{ __typename?: 'Query' }
& { user: (
Expand Down

0 comments on commit 0a90986

Please sign in to comment.