Skip to content

Commit

Permalink
Add entireFieldWrapperValue configuration option, to wrap arrays. (#…
Browse files Browse the repository at this point in the history
…5753)

* Add a new config to wrap array field definitions.

* Update test.

* Wrong.

* Add a test for nesting both options also.

* added missing changeset

Co-authored-by: Dotan Simha <[email protected]>
  • Loading branch information
Hampus Joakim Borgos and dotansimha authored Apr 19, 2021
1 parent 5c11943 commit f0b5ea5
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 1 deletion.
7 changes: 7 additions & 0 deletions .changeset/warm-candles-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@graphql-codegen/visitor-plugin-common': minor
'@graphql-codegen/typescript': minor
'@graphql-codegen/flow': minor
---

Add entireFieldWrapperValue configuration option, to wrap arrays
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export interface ParsedTypesConfig extends ParsedConfig {
enumPrefix: boolean;
fieldWrapperValue: string;
wrapFieldDefinitions: boolean;
entireFieldWrapperValue: string;
wrapEntireDefinitions: boolean;
ignoreEnumValuesFromSchema: boolean;
}

Expand Down Expand Up @@ -191,6 +193,44 @@ export interface RawTypesConfig extends RawConfig {
* ```
*/
ignoreEnumValuesFromSchema?: boolean;
/**
* @name wrapEntireFieldDefinitions
* @type boolean
* @description Set the to `true` in order to wrap field definitions with `EntireFieldWrapper`.
* This is useful to allow return types such as Promises and functions for fields.
* Differs from `wrapFieldDefinitions` in that this wraps the entire field definition if ie. the field is an Array, while
* `wrapFieldDefinitions` will wrap every single value inside the array.
* @default true
*
* @example Enable wrapping entire fields
* ```yml
* generates:
* path/to/file.ts:
* plugins:
* - typescript
* config:
* wrapEntireFieldDefinitions: false
* ```
*/
wrapEntireFieldDefinitions?: boolean;
/**
* @name entireFieldWrapperValue
* @type string
* @description Allow to override the type value of `EntireFieldWrapper`. This wrapper applies outside of Array and Maybe
* unlike `fieldWrapperValue`, that will wrap the inner type.
* @default T | Promise<T> | (() => T | Promise<T>)
*
* @example Only allow values
* ```yml
* generates:
* path/to/file.ts:
* plugins:
* - typescript
* config:
* entireFieldWrapperValue: T
* ```
*/
entireFieldWrapperValue?: string;
}

export class BaseTypesVisitor<
Expand Down Expand Up @@ -218,6 +258,8 @@ export class BaseTypesVisitor<
scalars: buildScalarsFromConfig(_schema, rawConfig, defaultScalars),
fieldWrapperValue: getConfigValue(rawConfig.fieldWrapperValue, 'T'),
wrapFieldDefinitions: getConfigValue(rawConfig.wrapFieldDefinitions, false),
entireFieldWrapperValue: getConfigValue(rawConfig.entireFieldWrapperValue, 'T'),
wrapEntireDefinitions: getConfigValue(rawConfig.wrapEntireFieldDefinitions, false),
ignoreEnumValuesFromSchema: getConfigValue(rawConfig.ignoreEnumValuesFromSchema, false),
...additionalConfig,
});
Expand All @@ -237,6 +279,14 @@ export class BaseTypesVisitor<
return '';
}

public getEntireFieldWrapperValue(): string {
if (this.config.entireFieldWrapperValue) {
return `${this.getExportPrefix()}type EntireFieldWrapper<T> = ${this.config.entireFieldWrapperValue};`;
}

return '';
}

public getScalarsImports(): string[] {
return Object.keys(this.config.scalars)
.map(enumName => {
Expand Down
38 changes: 38 additions & 0 deletions packages/plugins/typescript/typescript/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,4 +238,42 @@ export interface TypeScriptPluginConfig extends RawTypesConfig {
* ```
*/
useImplementingTypes?: boolean;
/**
* @name wrapEntireFieldDefinitions
* @type boolean
* @description Set the to `true` in order to wrap field definitions with `EntireFieldWrapper`.
* This is useful to allow return types such as Promises and functions for fields.
* Differs from `wrapFieldDefinitions` in that this wraps the entire field definition if ie. the field is an Array, while
* `wrapFieldDefinitions` will wrap every single value inside the array.
* @default true
*
* @example Enable wrapping entire fields
* ```yml
* generates:
* path/to/file.ts:
* plugins:
* - typescript
* config:
* wrapEntireFieldDefinitions: false
* ```
*/
wrapEntireFieldDefinitions?: boolean
/**
* @name entireFieldWrapperValue
* @type string
* @description Allow to override the type value of `EntireFieldWrapper`. This wrapper applies outside of Array and Maybe
* unlike `fieldWrapperValue`, that will wrap the inner type.
* @default T | Promise<T> | (() => T | Promise<T>)
*
* @example Only allow values
* ```yml
* generates:
* path/to/file.ts:
* plugins:
* - typescript
* config:
* entireFieldWrapperValue: T
* ```
*/;
entireFieldWrapperValue?: string;
}
9 changes: 8 additions & 1 deletion packages/plugins/typescript/typescript/src/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export class TsVisitor<
onlyOperationTypes: getConfigValue(pluginConfig.onlyOperationTypes, false),
immutableTypes: getConfigValue(pluginConfig.immutableTypes, false),
useImplementingTypes: getConfigValue(pluginConfig.useImplementingTypes, false),
entireFieldWrapperValue: getConfigValue(pluginConfig.entireFieldWrapperValue, 'T'),
wrapEntireDefinitions: getConfigValue(pluginConfig.wrapEntireFieldDefinitions, false),
...(additionalConfig || {}),
} as TParsedConfig);

Expand Down Expand Up @@ -126,6 +128,9 @@ export class TsVisitor<
if (this.config.wrapFieldDefinitions) {
definitions.push(this.getFieldWrapperValue());
}
if (this.config.wrapEntireDefinitions) {
definitions.push(this.getEntireFieldWrapperValue());
}

return definitions;
}
Expand Down Expand Up @@ -204,7 +209,9 @@ export class TsVisitor<
}

FieldDefinition(node: FieldDefinitionNode, key?: number | string, parent?: any): string {
const typeString = (node.type as any) as string;
const typeString = this.config.wrapEntireDefinitions
? `EntireFieldWrapper<${node.type}>`
: ((node.type as any) as string);
const originalFieldNode = parent[key] as FieldDefinitionNode;
const addOptionalSign = !this.config.avoidOptionals.field && originalFieldNode.type.kind !== Kind.NON_NULL_TYPE;
const comment = this.getFieldComment(node);
Expand Down
68 changes: 68 additions & 0 deletions packages/plugins/typescript/typescript/tests/typescript.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2073,6 +2073,74 @@ describe('TypeScript', () => {
validateTs(result);
});

it('Should build list type correctly when wrapping entire field definitions', async () => {
const schema = buildSchema(`
type ListOfStrings {
foo: [String!]!
}
type ListOfMaybeStrings {
foo: [String]!
}
`);
const result = (await plugin(
schema,
[],
{ wrapEntireFieldDefinitions: true },
{ outputFile: '' }
)) as Types.ComplexPluginOutput;

expect(result.content).toBeSimilarStringTo(`
export type ListOfStrings = {
__typename?: 'ListOfStrings';
foo: EntireFieldWrapper<Array<Scalars['String']>>;
};
`);

expect(result.content).toBeSimilarStringTo(`
export type ListOfMaybeStrings = {
__typename?: 'ListOfMaybeStrings';
foo: EntireFieldWrapper<Array<Maybe<Scalars['String']>>>;
};
`);

validateTs(result);
});

it('Should build list type correctly when wrapping both field definitions and entire field definitions', async () => {
const schema = buildSchema(`
type ListOfStrings {
foo: [String!]!
}
type ListOfMaybeStrings {
foo: [String]!
}
`);
const result = (await plugin(
schema,
[],
{ wrapEntireFieldDefinitions: true, wrapFieldDefinitions: true },
{ outputFile: '' }
)) as Types.ComplexPluginOutput;

expect(result.content).toBeSimilarStringTo(`
export type ListOfStrings = {
__typename?: 'ListOfStrings';
foo: EntireFieldWrapper<Array<FieldWrapper<Scalars['String']>>>;
};
`);

expect(result.content).toBeSimilarStringTo(`
export type ListOfMaybeStrings = {
__typename?: 'ListOfMaybeStrings';
foo: EntireFieldWrapper<Array<Maybe<FieldWrapper<Scalars['String']>>>>;
};
`);

validateTs(result);
});

it('Should not wrap input type fields', async () => {
const schema = buildSchema(`
input MyInput {
Expand Down

0 comments on commit f0b5ea5

Please sign in to comment.