Skip to content

Commit 48c9ae6

Browse files
committed
fixes for enums
1 parent 1f0a571 commit 48c9ae6

File tree

9 files changed

+167
-31
lines changed

9 files changed

+167
-31
lines changed

packages/plugins/flow/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,5 @@ export const plugin: PluginFunction<FlowPluginConfig> = (schema: GraphQLSchema,
5353
leave: visitor,
5454
});
5555

56-
return [header, visitor.scalarsDefinition, ...visitorResult.definitions].join('\n');
56+
return [header, visitor.getEnumsImports(), visitor.scalarsDefinition, ...visitorResult.definitions].join('\n');
5757
};

packages/plugins/flow/src/visitor.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,17 @@ export class FlowVisitor extends BaseTypesVisitor<FlowPluginConfig, FlowPluginPa
6363
);
6464
}
6565

66+
protected _buildEnumImport(identifier: string, source: string): string {
67+
return `import { type ${identifier} } from '${source}';`;
68+
}
69+
6670
EnumTypeDefinition(node: EnumTypeDefinitionNode): string {
71+
const typeName = (node.name as any) as string;
72+
73+
if (this.config.enumValues[typeName] && typeof this.config.enumValues[typeName] === 'string') {
74+
return null;
75+
}
76+
6777
const enumValuesName = this.convertName(node, {
6878
suffix: 'Values',
6979
});
@@ -73,7 +83,20 @@ export class FlowVisitor extends BaseTypesVisitor<FlowPluginConfig, FlowPluginPa
7383
.asKind('const')
7484
.withName(enumValuesName)
7585
.withMethodCall('Object.freeze')
76-
.withBlock(node.values.map(enumOption => indent(`${this.convertName(enumOption)}: ${wrapWithSingleQuotes(this._parsedConfig.enumValues[(enumOption.name as any) as string] || enumOption.name)}`)).join(', \n')).string;
86+
.withBlock(
87+
node.values
88+
.map(enumOption => {
89+
const optionName = this.convertName(enumOption);
90+
let enumValue: string = (enumOption.name as any) as string;
91+
92+
if (this.config.enumValues[typeName] && typeof this.config.enumValues[typeName] === 'object' && this.config.enumValues[typeName][enumValue]) {
93+
enumValue = this.config.enumValues[typeName][enumValue];
94+
}
95+
96+
return indent(`${optionName}: ${wrapWithSingleQuotes(enumValue)}`);
97+
})
98+
.join(', \n')
99+
).string;
77100

78101
const enumType = new DeclarationBlock(this._declarationBlockConfig)
79102
.export()

packages/plugins/flow/tests/flow.spec.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ describe('Flow Plugin', () => {
384384

385385
it('Should build enum correctly with custom values', async () => {
386386
const schema = buildSchema(`enum MyEnum { A, B, C }`);
387-
const result = await plugin(schema, [], { enumValues: { A: 'SomeValue', B: 'TEST' } }, { outputFile: '' });
387+
const result = await plugin(schema, [], { enumValues: { MyEnum: { A: 'SomeValue', B: 'TEST' } } }, { outputFile: '' });
388388

389389
expect(result).toBeSimilarStringTo(`
390390
export const MyEnumValues = Object.freeze({
@@ -398,6 +398,26 @@ describe('Flow Plugin', () => {
398398

399399
validateFlow(result);
400400
});
401+
402+
it('Should build enum correctly with custom values and map to external enum', async () => {
403+
const schema = buildSchema(`enum MyEnum { A, B, C }`);
404+
const result = await plugin(schema, [], { enumValues: { MyEnum: './my-file#MyEnum' } }, { outputFile: '' });
405+
406+
expect(result).not.toContain(`export type MyEnum`);
407+
expect(result).toContain(`import { type MyEnum } from './my-file';`);
408+
409+
validateFlow(result);
410+
});
411+
412+
it('Should build enum correctly with custom values and map to external enum with different identifier', async () => {
413+
const schema = buildSchema(`enum MyEnum { A, B, C }`);
414+
const result = await plugin(schema, [], { enumValues: { MyEnum: './my-file#MyCustomEnum' } }, { outputFile: '' });
415+
416+
expect(result).not.toContain(`export type MyEnum`);
417+
expect(result).toContain(`import { type MyCustomEnum as MyEnum } from './my-file';`);
418+
419+
validateFlow(result);
420+
});
401421
});
402422

403423
describe('Scalars', () => {

packages/plugins/typescript/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,12 @@ export const plugin: PluginFunction<TypeScriptPluginConfig> = (schema: GraphQLSc
101101
const visitor = new TsVisitor(schema, config);
102102
const printedSchema = printSchema(schema);
103103
const astNode = parse(printedSchema);
104-
const header = `type Maybe<T> = ${visitor.config.maybeValue};`;
104+
const maybeValue = `type Maybe<T> = ${visitor.config.maybeValue};`;
105105
const visitorResult = visit(astNode, { leave: visitor });
106106
const introspectionDefinitions = includeIntrospectionDefinitions(schema, documents, config);
107107
const scalars = visitor.scalarsDefinition;
108108

109-
return [header, scalars, ...visitorResult.definitions, ...introspectionDefinitions].join('\n');
109+
return [visitor.getEnumsImports(), maybeValue, scalars, ...visitorResult.definitions, ...introspectionDefinitions].join('\n');
110110
};
111111

112112
function includeIntrospectionDefinitions(schema: GraphQLSchema, documents: Types.DocumentFile[], config: TypeScriptPluginConfig): string[] {

packages/plugins/typescript/src/visitor.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DeclarationBlock, indent, BaseTypesVisitor, ParsedTypesConfig } from '@graphql-codegen/visitor-plugin-common';
1+
import { wrapWithSingleQuotes, DeclarationBlock, indent, BaseTypesVisitor, ParsedTypesConfig } from '@graphql-codegen/visitor-plugin-common';
22
import { TypeScriptPluginConfig } from './index';
33
import * as autoBind from 'auto-bind';
44
import { FieldDefinitionNode, NamedTypeNode, ListTypeNode, NonNullTypeNode, EnumTypeDefinitionNode, Kind, InputValueDefinitionNode, GraphQLSchema } from 'graphql';
@@ -72,18 +72,37 @@ export class TsVisitor<TRawConfig extends TypeScriptPluginConfig = TypeScriptPlu
7272
}
7373

7474
EnumTypeDefinition(node: EnumTypeDefinitionNode): string {
75+
const enumName = (node.name as any) as string;
76+
77+
// In case of mapped external enum string
78+
if (this.config.enumValues[enumName] && typeof this.config.enumValues[enumName] === 'string') {
79+
return null;
80+
}
81+
7582
if (this.config.enumsAsTypes) {
7683
return new DeclarationBlock(this._declarationBlockConfig)
7784
.export()
7885
.asKind('type')
7986
.withName(this.convertName(node))
80-
.withContent(node.values.map(v => `'${this.config.enumValues[(v.name as any) as string] || v.name}'`).join(' | ')).string;
87+
.withContent(
88+
node.values
89+
.map(enumOption => {
90+
let enumValue: string = (enumOption.name as any) as string;
91+
92+
if (this.config.enumValues[enumName] && typeof this.config.enumValues[enumName] === 'object' && this.config.enumValues[enumName][enumValue]) {
93+
enumValue = this.config.enumValues[enumName][enumValue];
94+
}
95+
96+
return wrapWithSingleQuotes(enumValue);
97+
})
98+
.join(' | ')
99+
).string;
81100
} else {
82101
return new DeclarationBlock(this._declarationBlockConfig)
83102
.export()
84103
.asKind(this.config.constEnums ? 'const enum' : 'enum')
85104
.withName(this.convertName(node))
86-
.withBlock(this.buildEnumValuesBlock(node.values)).string;
105+
.withBlock(this.buildEnumValuesBlock(enumName, node.values)).string;
87106
}
88107
}
89108
}

packages/plugins/typescript/tests/typescript.spec.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ describe('TypeScript', () => {
4141
validateTs(result);
4242
});
4343
});
44+
4445
describe('Config', () => {
4546
it('Should build type correctly when specified with avoidOptionals config', async () => {
4647
const schema = buildSchema(`
@@ -109,7 +110,7 @@ describe('TypeScript', () => {
109110
A
110111
B
111112
}`);
112-
const result = await plugin(schema, [], { enumValues: { A: 'BOOP' }, enumsAsTypes: true }, { outputFile: '' });
113+
const result = await plugin(schema, [], { enumValues: { MyEnum: { A: 'BOOP' } }, enumsAsTypes: true }, { outputFile: '' });
113114

114115
expect(result).toBeSimilarStringTo(`
115116
export type MyEnum = 'BOOP' | 'B';
@@ -851,7 +852,7 @@ describe('TypeScript', () => {
851852

852853
it('Should build enum correctly with custom values', async () => {
853854
const schema = buildSchema(`enum MyEnum { A, B, C }`);
854-
const result = await plugin(schema, [], { enumValues: { A: 'SomeValue', B: 'TEST' } }, { outputFile: '' });
855+
const result = await plugin(schema, [], { enumValues: { MyEnum: { A: 'SomeValue', B: 'TEST' } } }, { outputFile: '' });
855856

856857
expect(result).toBeSimilarStringTo(`
857858
export enum MyEnum {
@@ -863,6 +864,26 @@ describe('TypeScript', () => {
863864

864865
validateTs(result);
865866
});
867+
868+
it('Should build enum correctly with custom imported enum', async () => {
869+
const schema = buildSchema(`enum MyEnum { A, B, C }`);
870+
const result = await plugin(schema, [], { enumValues: { MyEnum: './my-file#MyEnum' } }, { outputFile: '' });
871+
872+
expect(result).not.toContain(`export enum MyEnum`);
873+
expect(result).toContain(`import { MyEnum } from './my-file';`);
874+
875+
validateTs(result);
876+
});
877+
878+
it('Should build enum correctly with custom imported enum with different name', async () => {
879+
const schema = buildSchema(`enum MyEnum { A, B, C }`);
880+
const result = await plugin(schema, [], { enumValues: { MyEnum: './my-file#MyCustomEnum' } }, { outputFile: '' });
881+
882+
expect(result).not.toContain(`export enum MyEnum`);
883+
expect(result).toContain(`import { MyCustomEnum as MyEnum } from './my-file';`);
884+
885+
validateTs(result);
886+
});
866887
});
867888

868889
it('should not have [object Object]', async () => {

packages/plugins/visitor-plugin-common/src/base-types-visitor.ts

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
1-
import { BaseVisitor, ParsedConfig, RawConfig } from './base-visitor';
2-
import { EnumValuesMap, ScalarsMap } from './types';
3-
import { OperationVariablesToObject } from './variables-to-object';
4-
import { DeclarationBlockConfig, DeclarationBlock, indent, wrapWithSingleQuotes, buildScalars } from './utils';
51
import {
2+
DirectiveDefinitionNode,
3+
EnumTypeDefinitionNode,
4+
EnumValueDefinitionNode,
5+
FieldDefinitionNode,
6+
GraphQLSchema,
67
InputObjectTypeDefinitionNode,
78
InputValueDefinitionNode,
8-
NameNode,
9-
FieldDefinitionNode,
10-
ObjectTypeDefinitionNode,
11-
EnumTypeDefinitionNode,
12-
DirectiveDefinitionNode,
9+
InterfaceTypeDefinitionNode,
1310
ListTypeNode,
14-
GraphQLSchema,
11+
NamedTypeNode,
12+
NameNode,
1513
NonNullTypeNode,
16-
UnionTypeDefinitionNode,
17-
InterfaceTypeDefinitionNode,
14+
ObjectTypeDefinitionNode,
1815
ScalarTypeDefinitionNode,
19-
EnumValueDefinitionNode,
20-
NamedTypeNode,
16+
UnionTypeDefinitionNode,
2117
} from 'graphql';
18+
import { BaseVisitor, ParsedConfig, RawConfig } from './base-visitor';
19+
import { parseMapper } from './mappers';
2220
import { DEFAULT_SCALARS } from './scalars';
21+
import { EnumValuesMap, ScalarsMap } from './types';
22+
import { buildScalars, DeclarationBlock, DeclarationBlockConfig, indent, wrapWithSingleQuotes } from './utils';
23+
import { OperationVariablesToObject } from './variables-to-object';
2324

2425
export interface ParsedTypesConfig extends ParsedConfig {
2526
enumValues: EnumValuesMap;
@@ -30,14 +31,22 @@ export interface RawTypesConfig extends RawConfig {
3031
* @name enumValues
3132
* @type EnumValuesMap
3233
* @description Overrides the default value of enum values declared in your GraphQL schema.
34+
* You can also map the entire enum to an external type by providing a string that of `module#type`.
3335
*
34-
* @example
36+
* @example With Custom Values
3537
* ```yml
3638
* config:
3739
* enumValues:
3840
* MyEnum:
3941
* A: 'foo'
4042
* ```
43+
*
44+
* @example With External Enum
45+
* ```yml
46+
* config:
47+
* enumValues:
48+
* MyEnum: ./my-file#MyCustomEnum
49+
* ```
4150
*/
4251
enumValues?: EnumValuesMap;
4352
}
@@ -163,16 +172,59 @@ export class BaseTypesVisitor<TRawConfig extends RawTypesConfig = RawTypesConfig
163172
return '';
164173
}
165174

175+
protected _buildEnumImport(identifier: string, source: string): string {
176+
return `import { ${identifier} } from '${source}';`;
177+
}
178+
179+
public getEnumsImports(): string {
180+
return Object.keys(this.config.enumValues)
181+
.map(enumName => {
182+
const mappedValue = this.config.enumValues[enumName];
183+
184+
if (mappedValue && typeof mappedValue === 'string') {
185+
const mapper = parseMapper(mappedValue);
186+
187+
if (mapper.isExternal) {
188+
const identifier = mapper.type === enumName ? enumName : `${mapper.type} as ${enumName}`;
189+
190+
return this._buildEnumImport(identifier, mapper.source);
191+
}
192+
}
193+
194+
return null;
195+
})
196+
.filter(a => a)
197+
.join('\n');
198+
}
199+
166200
EnumTypeDefinition(node: EnumTypeDefinitionNode): string {
201+
const enumName = (node.name as any) as string;
202+
203+
// In case of mapped external enum string
204+
if (this.config.enumValues[enumName] && typeof this.config.enumValues[enumName] === 'string') {
205+
return null;
206+
}
207+
167208
return new DeclarationBlock(this._declarationBlockConfig)
168209
.export()
169210
.asKind('enum')
170211
.withName(this.convertName(node))
171-
.withBlock(this.buildEnumValuesBlock(node.values)).string;
212+
.withBlock(this.buildEnumValuesBlock(enumName, node.values)).string;
172213
}
173214

174-
protected buildEnumValuesBlock(values: ReadonlyArray<EnumValueDefinitionNode>): string {
175-
return values.map(enumOption => indent(`${this.convertName(enumOption)}${this._declarationBlockConfig.enumNameValueSeparator} ${wrapWithSingleQuotes(this.config.enumValues[(enumOption.name as any) as string] || enumOption.name)}`)).join(',\n');
215+
protected buildEnumValuesBlock(typeName: string, values: ReadonlyArray<EnumValueDefinitionNode>): string {
216+
return values
217+
.map(enumOption => {
218+
const optionName = this.convertName(enumOption);
219+
let enumValue: string = (enumOption.name as any) as string;
220+
221+
if (this.config.enumValues[typeName] && typeof this.config.enumValues[typeName] === 'object' && this.config.enumValues[typeName][enumValue]) {
222+
enumValue = this.config.enumValues[typeName][enumValue];
223+
}
224+
225+
return indent(`${optionName}${this._declarationBlockConfig.enumNameValueSeparator} ${wrapWithSingleQuotes(enumValue)}`);
226+
})
227+
.join(',\n');
176228
}
177229

178230
DirectiveDefinition(node: DirectiveDefinitionNode): string {

packages/plugins/visitor-plugin-common/src/mappers.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,17 @@ export interface ParsedMapper {
99
export function parseMapper(mapper: string): ParsedMapper {
1010
if (isExternalMapper(mapper)) {
1111
const [source, type] = mapper.split('#');
12+
1213
return {
1314
isExternal: true,
1415
source,
15-
type
16+
type,
1617
};
1718
}
1819

1920
return {
2021
isExternal: false,
21-
type: mapper
22+
type: mapper,
2223
};
2324
}
2425

packages/plugins/visitor-plugin-common/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ASTNode } from 'graphql';
22

33
export type ScalarsMap = { [name: string]: string };
4-
export type EnumValuesMap = { [key: string]: string };
4+
export type EnumValuesMap = { [enumName: string]: string | { [key: string]: string } };
55
export type ConvertNameFn<T = {}> = ConvertFn<T>;
66

77
export interface ConvertOptions {

0 commit comments

Comments
 (0)