UNPKG

15.6 kBJavaScriptView Raw
1import { transformComment, wrapWithSingleQuotes, DeclarationBlock, indent, BaseTypesVisitor, getConfigValue, normalizeAvoidOptionals, isOneOfInputObjectType, } from '@graphql-codegen/visitor-plugin-common';
2import autoBind from 'auto-bind';
3import { Kind, isEnumType, GraphQLObjectType, } from 'graphql';
4import { TypeScriptOperationVariablesToObject } from './typescript-variables-to-object.js';
5export const EXACT_SIGNATURE = `type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };`;
6export const MAKE_OPTIONAL_SIGNATURE = `type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };`;
7export const MAKE_MAYBE_SIGNATURE = `type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };`;
8export class TsVisitor extends BaseTypesVisitor {
9 constructor(schema, pluginConfig, additionalConfig = {}) {
10 super(schema, pluginConfig, {
11 noExport: getConfigValue(pluginConfig.noExport, false),
12 avoidOptionals: normalizeAvoidOptionals(getConfigValue(pluginConfig.avoidOptionals, false)),
13 maybeValue: getConfigValue(pluginConfig.maybeValue, 'T | null'),
14 inputMaybeValue: getConfigValue(pluginConfig.inputMaybeValue, getConfigValue(pluginConfig.maybeValue, 'Maybe<T>')),
15 constEnums: getConfigValue(pluginConfig.constEnums, false),
16 enumsAsTypes: getConfigValue(pluginConfig.enumsAsTypes, false),
17 futureProofEnums: getConfigValue(pluginConfig.futureProofEnums, false),
18 futureProofUnions: getConfigValue(pluginConfig.futureProofUnions, false),
19 enumsAsConst: getConfigValue(pluginConfig.enumsAsConst, false),
20 numericEnums: getConfigValue(pluginConfig.numericEnums, false),
21 onlyEnums: getConfigValue(pluginConfig.onlyEnums, false),
22 onlyOperationTypes: getConfigValue(pluginConfig.onlyOperationTypes, false),
23 immutableTypes: getConfigValue(pluginConfig.immutableTypes, false),
24 useImplementingTypes: getConfigValue(pluginConfig.useImplementingTypes, false),
25 entireFieldWrapperValue: getConfigValue(pluginConfig.entireFieldWrapperValue, 'T'),
26 wrapEntireDefinitions: getConfigValue(pluginConfig.wrapEntireFieldDefinitions, false),
27 ...additionalConfig,
28 });
29 autoBind(this);
30 const enumNames = Object.values(schema.getTypeMap())
31 .filter(isEnumType)
32 .map(type => type.name);
33 this.setArgumentsTransformer(new TypeScriptOperationVariablesToObject(this.scalars, this.convertName, this.config.avoidOptionals, this.config.immutableTypes, null, enumNames, pluginConfig.enumPrefix, this.config.enumValues, false, this.config.directiveArgumentAndInputFieldMappings, 'InputMaybe'));
34 this.setDeclarationBlockConfig({
35 enumNameValueSeparator: ' =',
36 ignoreExport: this.config.noExport,
37 });
38 }
39 _getTypeForNode(node) {
40 const typeAsString = node.name;
41 if (this.config.useImplementingTypes) {
42 const allTypesMap = this._schema.getTypeMap();
43 const implementingTypes = [];
44 // TODO: Move this to a better place, since we are using this logic in some other places as well.
45 for (const graphqlType of Object.values(allTypesMap)) {
46 if (graphqlType instanceof GraphQLObjectType) {
47 const allInterfaces = graphqlType.getInterfaces();
48 if (allInterfaces.some(int => typeAsString === int.name)) {
49 implementingTypes.push(this.convertName(graphqlType.name));
50 }
51 }
52 }
53 if (implementingTypes.length > 0) {
54 return implementingTypes.join(' | ');
55 }
56 }
57 const typeString = super._getTypeForNode(node);
58 const schemaType = this._schema.getType(node.name);
59 if (isEnumType(schemaType)) {
60 // futureProofEnums + enumsAsTypes combination adds the future value to the enum type itself
61 // so it's not necessary to repeat it in the usage
62 const futureProofEnumUsageEnabled = this.config.futureProofEnums === true && this.config.enumsAsTypes !== true;
63 if (futureProofEnumUsageEnabled && this.config.allowEnumStringTypes === true) {
64 return `${typeString} | '%future added value' | ` + '`${' + typeString + '}`';
65 }
66 if (futureProofEnumUsageEnabled) {
67 return `${typeString} | '%future added value'`;
68 }
69 if (this.config.allowEnumStringTypes === true) {
70 return `${typeString} | ` + '`${' + typeString + '}`';
71 }
72 }
73 return typeString;
74 }
75 getWrapperDefinitions() {
76 if (this.config.onlyEnums)
77 return [];
78 const definitions = [
79 this.getMaybeValue(),
80 this.getInputMaybeValue(),
81 this.getExactDefinition(),
82 this.getMakeOptionalDefinition(),
83 this.getMakeMaybeDefinition(),
84 ];
85 if (this.config.wrapFieldDefinitions) {
86 definitions.push(this.getFieldWrapperValue());
87 }
88 if (this.config.wrapEntireDefinitions) {
89 definitions.push(this.getEntireFieldWrapperValue());
90 }
91 return definitions;
92 }
93 getExactDefinition() {
94 if (this.config.onlyEnums)
95 return '';
96 return `${this.getExportPrefix()}${EXACT_SIGNATURE}`;
97 }
98 getMakeOptionalDefinition() {
99 return `${this.getExportPrefix()}${MAKE_OPTIONAL_SIGNATURE}`;
100 }
101 getMakeMaybeDefinition() {
102 if (this.config.onlyEnums)
103 return '';
104 return `${this.getExportPrefix()}${MAKE_MAYBE_SIGNATURE}`;
105 }
106 getMaybeValue() {
107 return `${this.getExportPrefix()}type Maybe<T> = ${this.config.maybeValue};`;
108 }
109 getInputMaybeValue() {
110 return `${this.getExportPrefix()}type InputMaybe<T> = ${this.config.inputMaybeValue};`;
111 }
112 clearOptional(str) {
113 if (str.startsWith('Maybe')) {
114 return str.replace(/Maybe<(.*?)>$/, '$1');
115 }
116 if (str.startsWith('InputMaybe')) {
117 return str.replace(/InputMaybe<(.*?)>$/, '$1');
118 }
119 return str;
120 }
121 getExportPrefix() {
122 if (this.config.noExport) {
123 return '';
124 }
125 return super.getExportPrefix();
126 }
127 getMaybeWrapper(ancestors) {
128 const currentVisitContext = this.getVisitorKindContextFromAncestors(ancestors);
129 const isInputContext = currentVisitContext.includes(Kind.INPUT_OBJECT_TYPE_DEFINITION);
130 return isInputContext ? 'InputMaybe' : 'Maybe';
131 }
132 NamedType(node, key, parent, path, ancestors) {
133 return `${this.getMaybeWrapper(ancestors)}<${super.NamedType(node, key, parent, path, ancestors)}>`;
134 }
135 ListType(node, key, parent, path, ancestors) {
136 return `${this.getMaybeWrapper(ancestors)}<${super.ListType(node, key, parent, path, ancestors)}>`;
137 }
138 UnionTypeDefinition(node, key, parent) {
139 if (this.config.onlyOperationTypes || this.config.onlyEnums)
140 return '';
141 let withFutureAddedValue = [];
142 if (this.config.futureProofUnions) {
143 withFutureAddedValue = [
144 this.config.immutableTypes ? `{ readonly __typename?: "%other" }` : `{ __typename?: "%other" }`,
145 ];
146 }
147 const originalNode = parent[key];
148 const possibleTypes = originalNode.types
149 .map(t => (this.scalars[t.name.value] ? this._getScalar(t.name.value) : this.convertName(t)))
150 .concat(...withFutureAddedValue)
151 .join(' | ');
152 return new DeclarationBlock(this._declarationBlockConfig)
153 .export()
154 .asKind('type')
155 .withName(this.convertName(node))
156 .withComment(node.description)
157 .withContent(possibleTypes).string;
158 // return super.UnionTypeDefinition(node, key, parent).concat(withFutureAddedValue).join("");
159 }
160 wrapWithListType(str) {
161 return `${this.config.immutableTypes ? 'ReadonlyArray' : 'Array'}<${str}>`;
162 }
163 NonNullType(node) {
164 const baseValue = super.NonNullType(node);
165 return this.clearOptional(baseValue);
166 }
167 FieldDefinition(node, key, parent) {
168 const typeString = this.config.wrapEntireDefinitions
169 ? `EntireFieldWrapper<${node.type}>`
170 : node.type;
171 const originalFieldNode = parent[key];
172 const addOptionalSign = !this.config.avoidOptionals.field && originalFieldNode.type.kind !== Kind.NON_NULL_TYPE;
173 const comment = this.getNodeComment(node);
174 const { type } = this.config.declarationKind;
175 return (comment +
176 indent(`${this.config.immutableTypes ? 'readonly ' : ''}${node.name}${addOptionalSign ? '?' : ''}: ${typeString}${this.getPunctuation(type)}`));
177 }
178 InputValueDefinition(node, key, parent, _path, ancestors) {
179 const originalFieldNode = parent[key];
180 const addOptionalSign = !this.config.avoidOptionals.inputValue &&
181 (originalFieldNode.type.kind !== Kind.NON_NULL_TYPE ||
182 (!this.config.avoidOptionals.defaultValue && node.defaultValue !== undefined));
183 const comment = this.getNodeComment(node);
184 const declarationKind = this.config.declarationKind.type;
185 let type = node.type;
186 if (node.directives && this.config.directiveArgumentAndInputFieldMappings) {
187 type = this._getDirectiveOverrideType(node.directives) || type;
188 }
189 const readonlyPrefix = this.config.immutableTypes ? 'readonly ' : '';
190 const buildFieldDefinition = (isOneOf = false) => {
191 return `${readonlyPrefix}${node.name}${addOptionalSign && !isOneOf ? '?' : ''}: ${isOneOf ? this.clearOptional(type) : type}${this.getPunctuation(declarationKind)}`;
192 };
193 const realParentDef = ancestors === null || ancestors === void 0 ? void 0 : ancestors[ancestors.length - 1];
194 if (realParentDef) {
195 const parentType = this._schema.getType(realParentDef.name.value);
196 if (isOneOfInputObjectType(parentType)) {
197 if (originalFieldNode.type.kind === Kind.NON_NULL_TYPE) {
198 throw new Error('Fields on an input object type can not be non-nullable. It seems like the schema was not validated.');
199 }
200 const fieldParts = [];
201 for (const fieldName of Object.keys(parentType.getFields())) {
202 // Why the heck is node.name a string and not { value: string } at runtime ?!
203 if (fieldName === node.name) {
204 fieldParts.push(buildFieldDefinition(true));
205 continue;
206 }
207 fieldParts.push(`${readonlyPrefix}${fieldName}?: never;`);
208 }
209 return comment + indent(`{ ${fieldParts.join(' ')} }`);
210 }
211 }
212 return comment + indent(buildFieldDefinition());
213 }
214 EnumTypeDefinition(node) {
215 const enumName = node.name;
216 // In case of mapped external enum string
217 if (this.config.enumValues[enumName] && this.config.enumValues[enumName].sourceFile) {
218 return `export { ${this.config.enumValues[enumName].typeIdentifier} };\n`;
219 }
220 const getValueFromConfig = (enumValue) => {
221 if (this.config.enumValues[enumName] &&
222 this.config.enumValues[enumName].mappedValues &&
223 typeof this.config.enumValues[enumName].mappedValues[enumValue] !== 'undefined') {
224 return this.config.enumValues[enumName].mappedValues[enumValue];
225 }
226 return null;
227 };
228 const withFutureAddedValue = [
229 this.config.futureProofEnums ? [indent('| ' + wrapWithSingleQuotes('%future added value'))] : [],
230 ];
231 const enumTypeName = this.convertName(node, {
232 useTypesPrefix: this.config.enumPrefix,
233 });
234 if (this.config.enumsAsTypes) {
235 return new DeclarationBlock(this._declarationBlockConfig)
236 .export()
237 .asKind('type')
238 .withComment(node.description)
239 .withName(enumTypeName)
240 .withContent('\n' +
241 node.values
242 .map(enumOption => {
243 var _a;
244 const name = enumOption.name;
245 const enumValue = (_a = getValueFromConfig(name)) !== null && _a !== void 0 ? _a : name;
246 const comment = transformComment(enumOption.description, 1);
247 return comment + indent('| ' + wrapWithSingleQuotes(enumValue));
248 })
249 .concat(...withFutureAddedValue)
250 .join('\n')).string;
251 }
252 if (this.config.numericEnums) {
253 const block = new DeclarationBlock(this._declarationBlockConfig)
254 .export()
255 .withComment(node.description)
256 .withName(enumTypeName)
257 .asKind('enum')
258 .withBlock(node.values
259 .map((enumOption, i) => {
260 const valueFromConfig = getValueFromConfig(enumOption.name);
261 const enumValue = valueFromConfig !== null && valueFromConfig !== void 0 ? valueFromConfig : i;
262 const comment = transformComment(enumOption.description, 1);
263 const optionName = this.makeValidEnumIdentifier(this.convertName(enumOption, {
264 useTypesPrefix: false,
265 transformUnderscore: true,
266 }));
267 return comment + indent(optionName) + ` = ${enumValue}`;
268 })
269 .concat(...withFutureAddedValue)
270 .join(',\n')).string;
271 return block;
272 }
273 if (this.config.enumsAsConst) {
274 const typeName = `export type ${enumTypeName} = typeof ${enumTypeName}[keyof typeof ${enumTypeName}];`;
275 const enumAsConst = new DeclarationBlock({
276 ...this._declarationBlockConfig,
277 blockTransformer: block => {
278 return block + ' as const';
279 },
280 })
281 .export()
282 .asKind('const')
283 .withName(enumTypeName)
284 .withComment(node.description)
285 .withBlock(node.values
286 .map(enumOption => {
287 var _a;
288 const optionName = this.convertName(enumOption, {
289 useTypesPrefix: false,
290 transformUnderscore: true,
291 });
292 const comment = transformComment(enumOption.description, 1);
293 const name = enumOption.name;
294 const enumValue = (_a = getValueFromConfig(name)) !== null && _a !== void 0 ? _a : name;
295 return comment + indent(`${optionName}: ${wrapWithSingleQuotes(enumValue)}`);
296 })
297 .join(',\n')).string;
298 return [enumAsConst, typeName].join('\n');
299 }
300 return new DeclarationBlock(this._declarationBlockConfig)
301 .export()
302 .asKind(this.config.constEnums ? 'const enum' : 'enum')
303 .withName(enumTypeName)
304 .withComment(node.description)
305 .withBlock(this.buildEnumValuesBlock(enumName, node.values)).string;
306 }
307 getPunctuation(_declarationKind) {
308 return ';';
309 }
310}