Skip to content

Commit 700f32f

Browse files
authored
[Typescript] enum as const: add support const assertions from T… (#3569)
* Typescript plugin: enumAsConst feature * add type gen
1 parent 393ec96 commit 700f32f

File tree

4 files changed

+91
-5
lines changed

4 files changed

+91
-5
lines changed

Diff for: docs/generated-config/typescript.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,21 @@ path/to/file.ts:
6060
enumsAsTypes: true
6161
```
6262

63+
### enumsAsConst (`boolean`, default value: `false`)
64+
65+
Generates enum as TypeScript `const assertions` instead of `enum`. This can even be used to enable enum-like patterns in plain JavaScript code if you choose not to use TypeScript’s enum construct.
66+
67+
#### Usage Example
68+
69+
```yml
70+
generates:
71+
path/to/file.ts:
72+
plugins:
73+
- typescript
74+
config:
75+
enumsAsConst: true
76+
```
77+
6378
### immutableTypes (`boolean`, default value: `false`)
6479

6580
Generates immutable types by adding `readonly` to properties and uses `ReadonlyArray`.
@@ -106,4 +121,4 @@ path/to/file.ts:
106121
- typescript
107122
config:
108123
noExport: true
109-
```
124+
```

Diff for: packages/plugins/typescript/typescript/src/config.ts

+17
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,23 @@ export interface TypeScriptPluginConfig extends RawTypesConfig {
6868
* ```
6969
*/
7070
enumsAsTypes?: boolean;
71+
/**
72+
* @name enumsAsConst
73+
* @type boolean
74+
* @description Generates enum as TypeScript `const assertions` instead of `enum`. This can even be used to enable enum-like patterns in plain JavaScript code if you choose not to use TypeScript’s enum construct.
75+
* @default false
76+
*
77+
* @example
78+
* ```yml
79+
* generates:
80+
* path/to/file.ts:
81+
* plugins:
82+
* - typescript
83+
* config:
84+
* enumsAsConst: true
85+
* ```
86+
*/
87+
enumsAsConst?: boolean;
7188
/**
7289
* @name fieldWrapperValue
7390
* @type string

Diff for: packages/plugins/typescript/typescript/src/visitor.ts

+37-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface TypeScriptPluginParsedConfig extends ParsedTypesConfig {
1010
avoidOptionals: AvoidOptionalsConfig;
1111
constEnums: boolean;
1212
enumsAsTypes: boolean;
13+
enumsAsConst: boolean;
1314
fieldWrapperValue: string;
1415
immutableTypes: boolean;
1516
maybeValue: string;
@@ -26,6 +27,7 @@ export class TsVisitor<TRawConfig extends TypeScriptPluginConfig = TypeScriptPlu
2627
fieldWrapperValue: getConfigValue(pluginConfig.fieldWrapperValue, 'T'),
2728
constEnums: getConfigValue(pluginConfig.constEnums, false),
2829
enumsAsTypes: getConfigValue(pluginConfig.enumsAsTypes, false),
30+
enumsAsConst: getConfigValue(pluginConfig.enumsAsConst, false),
2931
immutableTypes: getConfigValue(pluginConfig.immutableTypes, false),
3032
wrapFieldDefinitions: getConfigValue(pluginConfig.wrapFieldDefinitions, false),
3133
...(additionalConfig || {}),
@@ -133,14 +135,45 @@ export class TsVisitor<TRawConfig extends TypeScriptPluginConfig = TypeScriptPlu
133135
})
134136
.join(' |\n')
135137
).string;
136-
} else {
137-
return new DeclarationBlock(this._declarationBlockConfig)
138+
}
139+
140+
if (this.config.enumsAsConst) {
141+
const typeName = `export type ${enumTypeName} = typeof ${enumTypeName}[keyof typeof ${enumTypeName}];`;
142+
const enumAsConst = new DeclarationBlock({
143+
...this._declarationBlockConfig,
144+
blockTransformer: block => {
145+
return block + ' as const';
146+
},
147+
})
138148
.export()
139-
.asKind(this.config.constEnums ? 'const enum' : 'enum')
149+
.asKind('const')
140150
.withName(enumTypeName)
141151
.withComment((node.description as any) as string)
142-
.withBlock(this.buildEnumValuesBlock(enumName, node.values)).string;
152+
.withBlock(
153+
node.values
154+
.map(enumOption => {
155+
const optionName = this.convertName(enumOption, { useTypesPrefix: false, transformUnderscore: true });
156+
const comment = transformComment((enumOption.description as any) as string, 1);
157+
let enumValue: string | number = enumOption.name as any;
158+
159+
if (this.config.enumValues[enumName] && this.config.enumValues[enumName].mappedValues && typeof this.config.enumValues[enumName].mappedValues[enumValue] !== 'undefined') {
160+
enumValue = this.config.enumValues[enumName].mappedValues[enumValue];
161+
}
162+
163+
return comment + indent(`${optionName}: ${wrapWithSingleQuotes(enumValue)}`);
164+
})
165+
.join(',\n')
166+
).string;
167+
168+
return [enumAsConst, typeName].join('\n');
143169
}
170+
171+
return new DeclarationBlock(this._declarationBlockConfig)
172+
.export()
173+
.asKind(this.config.constEnums ? 'const enum' : 'enum')
174+
.withName(enumTypeName)
175+
.withComment((node.description as any) as string)
176+
.withBlock(this.buildEnumValuesBlock(enumName, node.values)).string;
144177
}
145178

146179
protected getPunctuation(declarationKind: DeclarationKind): string {

Diff for: packages/plugins/typescript/typescript/tests/typescript.spec.ts

+21
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,27 @@ describe('TypeScript', () => {
202202
}`);
203203
});
204204

205+
it('Should work with enum as const', async () => {
206+
const schema = buildSchema(/* GraphQL */ `
207+
enum MyEnum {
208+
A_B_C
209+
X_Y_Z
210+
_TEST
211+
My_Value
212+
}
213+
`);
214+
const result = (await plugin(schema, [], { enumsAsConst: true }, { outputFile: '' })) as Types.ComplexPluginOutput;
215+
216+
expect(result.content).toBeSimilarStringTo(`
217+
export const MyEnum = {
218+
ABC: 'A_B_C',
219+
XYZ: 'X_Y_Z',
220+
Test: '_TEST',
221+
MyValue: 'My_Value'
222+
} as const;
223+
export type MyEnum = typeof MyEnum[keyof typeof MyEnum];`);
224+
});
225+
205226
it('Should work with enum and enum values (enumsAsTypes)', async () => {
206227
const schema = buildSchema(/* GraphQL */ `
207228
"custom enum"

0 commit comments

Comments
 (0)