Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add entireFieldWrapperValue configuration option, to wrap arrays. #5753

Merged
merged 5 commits into from
Apr 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: buildScalars(_schema, rawConfig.scalars, 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 @@ -2025,6 +2025,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