Skip to content

Commit be9829d

Browse files
committed
Extract convert enum name to utility
1 parent 85c99b2 commit be9829d

File tree

4 files changed

+276
-1
lines changed

4 files changed

+276
-1
lines changed
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
import type { EnumTypeDefinitionNode, EnumValueDefinitionNode, GraphQLEnumType, GraphQLSchema } from 'graphql';
2+
import type { ConvertFn, ParsedEnumValuesMap } from './types';
3+
import { DeclarationBlock, type DeclarationBlockConfig, indent, transformComment, wrapWithSingleQuotes } from './utils';
4+
import { getNodeComment } from './get-node-comment';
5+
6+
export const convertSchemaEnumToDeclarationBlockString = ({
7+
schema,
8+
node,
9+
enumName,
10+
enumValues,
11+
futureProofEnums,
12+
ignoreEnumValuesFromSchema,
13+
outputType,
14+
declarationBlockConfig,
15+
naming,
16+
}: {
17+
schema: GraphQLSchema;
18+
node: EnumTypeDefinitionNode;
19+
enumName: string;
20+
enumValues: ParsedEnumValuesMap;
21+
futureProofEnums: boolean;
22+
ignoreEnumValuesFromSchema: boolean;
23+
naming: {
24+
convert: ConvertFn;
25+
typesPrefix: string;
26+
typesSuffix: string;
27+
useTypesPrefix?: boolean;
28+
useTypesSuffix?: boolean;
29+
};
30+
31+
outputType: 'string-literal' | 'numeric' | 'const' | 'native-const' | 'native';
32+
declarationBlockConfig: DeclarationBlockConfig;
33+
}): string => {
34+
if (enumValues[enumName]?.sourceFile) {
35+
return `export { ${enumValues[enumName].typeIdentifier} };\n`;
36+
}
37+
38+
const getValueFromConfig = (enumValue: string | number) => {
39+
if (typeof enumValues[enumName]?.mappedValues?.[enumValue] !== 'undefined') {
40+
return enumValues[enumName].mappedValues[enumValue];
41+
}
42+
return null;
43+
};
44+
45+
const withFutureAddedValue = [futureProofEnums ? [indent('| ' + wrapWithSingleQuotes('%future added value'))] : []];
46+
47+
const enumTypeName = convertName({
48+
options: {
49+
typesPrefix: naming.typesPrefix,
50+
typesSuffix: naming.typesSuffix,
51+
useTypesPrefix: naming.useTypesPrefix,
52+
useTypesSuffix: naming.useTypesSuffix,
53+
},
54+
convert: () => naming.convert(node),
55+
});
56+
57+
if (outputType === 'string-literal') {
58+
return new DeclarationBlock(declarationBlockConfig)
59+
.export()
60+
.asKind('type')
61+
.withComment(node.description?.value)
62+
.withName(enumTypeName)
63+
.withContent(
64+
'\n' +
65+
node.values
66+
.map(enumOption => {
67+
const name = enumOption.name.value;
68+
const enumValue: string | number = getValueFromConfig(name) ?? name;
69+
const comment = transformComment(enumOption.description?.value, 1);
70+
71+
return comment + indent('| ' + wrapWithSingleQuotes(enumValue));
72+
})
73+
.concat(...withFutureAddedValue)
74+
.join('\n')
75+
).string;
76+
}
77+
78+
if (outputType === 'numeric') {
79+
return new DeclarationBlock(declarationBlockConfig)
80+
.export()
81+
.withComment(node.description?.value)
82+
.withName(enumTypeName)
83+
.asKind('enum')
84+
.withBlock(
85+
node.values
86+
.map((enumOption, i) => {
87+
const valueFromConfig = getValueFromConfig(enumOption.name.value);
88+
const enumValue: string | number = valueFromConfig ?? i;
89+
const comment = transformComment(enumOption.description?.value, 1);
90+
const optionName = makeValidEnumIdentifier(
91+
convertName({
92+
options: {
93+
typesPrefix: naming.typesPrefix,
94+
typesSuffix: naming.typesSuffix,
95+
useTypesPrefix: false,
96+
},
97+
convert: () => naming.convert(enumOption, { transformUnderscore: true }),
98+
})
99+
);
100+
return comment + indent(optionName) + ` = ${enumValue}`;
101+
})
102+
.concat(...withFutureAddedValue)
103+
.join(',\n')
104+
).string;
105+
}
106+
107+
if (outputType === 'const') {
108+
const typeName = `export type ${enumTypeName} = typeof ${enumTypeName}[keyof typeof ${enumTypeName}];`;
109+
const enumAsConst = new DeclarationBlock({
110+
...declarationBlockConfig,
111+
blockTransformer: block => {
112+
return block + ' as const';
113+
},
114+
})
115+
.export()
116+
.asKind('const')
117+
.withName(enumTypeName)
118+
.withComment(node.description?.value)
119+
.withBlock(
120+
node.values
121+
.map(enumOption => {
122+
const optionName = makeValidEnumIdentifier(
123+
convertName({
124+
options: {
125+
typesPrefix: naming.typesPrefix,
126+
typesSuffix: naming.typesPrefix,
127+
},
128+
convert: () =>
129+
naming.convert(enumOption, {
130+
transformUnderscore: true,
131+
}),
132+
})
133+
);
134+
const comment = transformComment(enumOption.description?.value, 1);
135+
const name = enumOption.name.value;
136+
const enumValue: string | number = getValueFromConfig(name) ?? name;
137+
138+
return comment + indent(`${optionName}: ${wrapWithSingleQuotes(enumValue)}`);
139+
})
140+
.join(',\n')
141+
).string;
142+
143+
return [enumAsConst, typeName].join('\n');
144+
}
145+
146+
const buildEnumValuesBlock = ({
147+
typeName,
148+
values,
149+
schema,
150+
}: {
151+
typeName: string;
152+
values: ReadonlyArray<EnumValueDefinitionNode>;
153+
schema: GraphQLSchema;
154+
}): string => {
155+
const schemaEnumType: GraphQLEnumType | undefined = schema
156+
? (schema.getType(typeName) as GraphQLEnumType)
157+
: undefined;
158+
159+
return values
160+
.map(enumOption => {
161+
const onlyUnderscoresPattern = /^_+$/;
162+
const optionName = makeValidEnumIdentifier(
163+
convertName({
164+
options: { typesPrefix: naming.typesPrefix, typesSuffix: naming.typesPrefix },
165+
convert: () =>
166+
naming.convert(enumOption, {
167+
// We can only strip out the underscores if the value contains other
168+
// characters. Otherwise we'll generate syntactically invalid code.
169+
transformUnderscore: !onlyUnderscoresPattern.test(enumOption.name.value),
170+
}),
171+
})
172+
);
173+
const comment = getNodeComment(enumOption);
174+
const schemaEnumValue =
175+
schemaEnumType && !ignoreEnumValuesFromSchema
176+
? schemaEnumType.getValue(enumOption.name.value).value
177+
: undefined;
178+
let enumValue: string | number =
179+
typeof schemaEnumValue === 'undefined' ? enumOption.name.value : schemaEnumValue;
180+
181+
if (typeof enumValues[typeName]?.mappedValues?.[enumValue] !== 'undefined') {
182+
enumValue = enumValues[typeName].mappedValues[enumValue];
183+
}
184+
185+
return (
186+
comment +
187+
indent(
188+
`${optionName}${declarationBlockConfig.enumNameValueSeparator} ${wrapWithSingleQuotes(
189+
enumValue,
190+
typeof schemaEnumValue !== 'undefined'
191+
)}`
192+
)
193+
);
194+
})
195+
.join(',\n');
196+
};
197+
198+
return new DeclarationBlock(declarationBlockConfig)
199+
.export()
200+
.asKind(outputType === 'native-const' ? 'const enum' : 'enum')
201+
.withName(enumTypeName)
202+
.withComment(node.description?.value)
203+
.withBlock(buildEnumValuesBlock({ typeName: enumName, values: node.values, schema })).string;
204+
};
205+
206+
const makeValidEnumIdentifier = (identifier: string): string => {
207+
if (/^[0-9]/.exec(identifier)) {
208+
return wrapWithSingleQuotes(identifier, true);
209+
}
210+
return identifier;
211+
};
212+
213+
const convertName = ({
214+
convert,
215+
options,
216+
}: {
217+
options: {
218+
typesPrefix: string;
219+
useTypesPrefix?: boolean;
220+
typesSuffix: string;
221+
useTypesSuffix?: boolean;
222+
};
223+
convert: () => string;
224+
}): string => {
225+
const useTypesPrefix = typeof options.useTypesPrefix === 'boolean' ? options.useTypesPrefix : true;
226+
const useTypesSuffix = typeof options.useTypesSuffix === 'boolean' ? options.useTypesSuffix : true;
227+
228+
let convertedName = '';
229+
230+
if (useTypesPrefix) {
231+
convertedName += options.typesPrefix;
232+
}
233+
234+
convertedName += convert();
235+
236+
if (useTypesSuffix) {
237+
convertedName += options.typesSuffix;
238+
}
239+
240+
return convertedName;
241+
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import {
2+
Kind,
3+
type DirectiveNode,
4+
type EnumValueDefinitionNode,
5+
type FieldDefinitionNode,
6+
type InputValueDefinitionNode,
7+
} from 'graphql';
8+
import { transformComment } from './utils';
9+
10+
export const getNodeComment = (
11+
node: FieldDefinitionNode | EnumValueDefinitionNode | InputValueDefinitionNode
12+
): string => {
13+
let commentText = node.description?.value;
14+
const deprecationDirective = node.directives.find(v => v.name.value === 'deprecated');
15+
if (deprecationDirective) {
16+
const deprecationReason = getDeprecationReason(deprecationDirective);
17+
commentText = `${commentText ? `${commentText}\n` : ''}@deprecated ${deprecationReason}`;
18+
}
19+
const comment = transformComment(commentText, 1);
20+
return comment;
21+
};
22+
23+
const getDeprecationReason = (directive: DirectiveNode): string | void => {
24+
if (directive.name.value === 'deprecated') {
25+
let reason = 'Field no longer supported';
26+
const deprecatedReason = directive.arguments[0];
27+
if (deprecatedReason && deprecatedReason.value.kind === Kind.STRING) {
28+
reason = deprecatedReason.value.value;
29+
}
30+
return reason;
31+
}
32+
};

packages/plugins/other/visitor-plugin-common/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,5 @@ export * from './selection-set-to-object.js';
1717
export * from './types.js';
1818
export * from './utils.js';
1919
export * from './variables-to-object.js';
20+
export { convertSchemaEnumToDeclarationBlockString } from './convert-schema-enum-to-declaration-block-string.js';
21+
export { getNodeComment } from './get-node-comment.js';

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export type ParsedEnumValuesMap = {
5959
isDefault?: boolean;
6060
};
6161
};
62-
export type ConvertNameFn<T = {}> = ConvertFn<T>;
62+
6363
export type GetFragmentSuffixFn = (node: FragmentDefinitionNode | string) => string;
6464

6565
export interface ConvertOptions {

0 commit comments

Comments
 (0)