UNPKG

9.87 kBJavaScriptView Raw
1import { Kind, isScalarType, isInputObjectType, isEnumType, isObjectType, visit } from 'graphql';
2import { getCachedDocumentNodeFromSchema } from '@graphql-codegen/plugin-helpers';
3import { BaseVisitor, buildScalarsFromConfig, indent, transformComment, indentMultiline, getBaseTypeNode } from '@graphql-codegen/visitor-plugin-common';
4import { wrapTypeWithModifiers, buildPackageNameFromPath } from '@graphql-codegen/java-common';
5import { dirname, normalize } from 'path';
6
7const KOTLIN_SCALARS = {
8 ID: 'Any',
9 String: 'String',
10 Boolean: 'Boolean',
11 Int: 'Int',
12 Float: 'Float',
13};
14class KotlinResolversVisitor extends BaseVisitor {
15 constructor(rawConfig, _schema, defaultPackageName) {
16 super(rawConfig, {
17 enumValues: rawConfig.enumValues || {},
18 listType: rawConfig.listType || 'Iterable',
19 withTypes: rawConfig.withTypes || false,
20 package: rawConfig.package || defaultPackageName,
21 scalars: buildScalarsFromConfig(_schema, rawConfig, KOTLIN_SCALARS),
22 });
23 this._schema = _schema;
24 }
25 getPackageName() {
26 return `package ${this.config.package}\n`;
27 }
28 getEnumValue(enumName, enumOption) {
29 if (this.config.enumValues[enumName] &&
30 typeof this.config.enumValues[enumName] === 'object' &&
31 this.config.enumValues[enumName][enumOption]) {
32 return this.config.enumValues[enumName][enumOption];
33 }
34 return enumOption;
35 }
36 EnumValueDefinition(node) {
37 return (enumName) => {
38 return indent(`${this.convertName(node, { useTypesPrefix: false, transformUnderscore: true })}("${this.getEnumValue(enumName, node.name.value)}")`);
39 };
40 }
41 EnumTypeDefinition(node) {
42 const comment = transformComment(node.description, 0);
43 const enumName = this.convertName(node.name);
44 const enumValues = indentMultiline(node.values.map(enumValue => enumValue(node.name.value)).join(',\n') + ';', 2);
45 return `${comment}enum class ${enumName}(val label: String) {
46${enumValues}
47
48 companion object {
49 @JvmStatic
50 fun valueOfLabel(label: String): ${enumName}? {
51 return values().find { it.label == label }
52 }
53 }
54}`;
55 }
56 resolveInputFieldType(typeNode) {
57 const innerType = getBaseTypeNode(typeNode);
58 const schemaType = this._schema.getType(innerType.name.value);
59 const isArray = typeNode.kind === Kind.LIST_TYPE ||
60 (typeNode.kind === Kind.NON_NULL_TYPE && typeNode.type.kind === Kind.LIST_TYPE);
61 let result = null;
62 const nullable = typeNode.kind !== Kind.NON_NULL_TYPE;
63 if (isScalarType(schemaType)) {
64 if (this.config.scalars[schemaType.name]) {
65 result = {
66 baseType: this.scalars[schemaType.name],
67 typeName: this.scalars[schemaType.name],
68 isScalar: true,
69 isArray,
70 nullable: nullable,
71 };
72 }
73 else {
74 result = { isArray, baseType: 'Any', typeName: 'Any', isScalar: true, nullable: nullable };
75 }
76 }
77 else if (isInputObjectType(schemaType)) {
78 const convertedName = this.convertName(schemaType.name);
79 const typeName = convertedName.endsWith('Input') ? convertedName : `${convertedName}Input`;
80 result = {
81 baseType: typeName,
82 typeName: typeName,
83 isScalar: false,
84 isArray,
85 nullable: nullable,
86 };
87 }
88 else if (isEnumType(schemaType) || isObjectType(schemaType)) {
89 result = {
90 isArray,
91 baseType: this.convertName(schemaType.name),
92 typeName: this.convertName(schemaType.name),
93 isScalar: true,
94 nullable: nullable,
95 };
96 }
97 else {
98 result = { isArray, baseType: 'Any', typeName: 'Any', isScalar: true, nullable: nullable };
99 }
100 if (result) {
101 result.typeName = wrapTypeWithModifiers(result.typeName, typeNode, this.config.listType);
102 }
103 return result;
104 }
105 buildInputTransfomer(name, inputValueArray) {
106 const classMembers = inputValueArray
107 .map(arg => {
108 const typeToUse = this.resolveInputFieldType(arg.type);
109 const initialValue = this.initialValue(typeToUse.typeName, arg.defaultValue);
110 const initial = initialValue ? ` = ${initialValue}` : typeToUse.nullable ? ' = null' : '';
111 return indent(`val ${arg.name.value}: ${typeToUse.typeName}${typeToUse.nullable ? '?' : ''}${initial}`, 2);
112 })
113 .join(',\n');
114 let suppress = '';
115 const ctorSet = inputValueArray
116 .map(arg => {
117 const typeToUse = this.resolveInputFieldType(arg.type);
118 const initialValue = this.initialValue(typeToUse.typeName, arg.defaultValue);
119 const fallback = initialValue ? ` ?: ${initialValue}` : '';
120 if (typeToUse.isArray && !typeToUse.isScalar) {
121 suppress = '@Suppress("UNCHECKED_CAST")\n ';
122 return indent(`args["${arg.name.value}"]${typeToUse.nullable || fallback ? '?' : '!!'}.let { ${arg.name.value} -> (${arg.name.value} as List<Map<String, Any>>).map { ${typeToUse.baseType}(it) } }${fallback}`, 3);
123 }
124 else if (typeToUse.isScalar) {
125 return indent(`args["${arg.name.value}"] as ${typeToUse.typeName}${typeToUse.nullable || fallback ? '?' : ''}${fallback}`, 3);
126 }
127 else if (typeToUse.nullable || fallback) {
128 suppress = '@Suppress("UNCHECKED_CAST")\n ';
129 return indent(`args["${arg.name.value}"]?.let { ${typeToUse.typeName}(it as Map<String, Any>) }${fallback}`, 3);
130 }
131 else {
132 suppress = '@Suppress("UNCHECKED_CAST")\n ';
133 return indent(`${typeToUse.typeName}(args["${arg.name.value}"] as Map<String, Any>)`, 3);
134 }
135 })
136 .join(',\n');
137 // language=kotlin
138 return `data class ${name}(
139${classMembers}
140) {
141 ${suppress}constructor(args: Map<String, Any>) : this(
142${ctorSet}
143 )
144}`;
145 }
146 buildTypeTransfomer(name, typeValueArray) {
147 const classMembers = typeValueArray
148 .map(arg => {
149 if (!arg.type) {
150 return '';
151 }
152 const typeToUse = this.resolveInputFieldType(arg.type);
153 return indent(`val ${arg.name.value}: ${typeToUse.typeName}${typeToUse.nullable ? '?' : ''}`, 2);
154 })
155 .join(',\n');
156 // language=kotlin
157 return `data class ${name}(
158${classMembers}
159)`;
160 }
161 initialValue(typeName, defaultValue) {
162 if (defaultValue) {
163 if (defaultValue.kind === 'IntValue' ||
164 defaultValue.kind === 'FloatValue' ||
165 defaultValue.kind === 'BooleanValue') {
166 return `${defaultValue.value}`;
167 }
168 else if (defaultValue.kind === 'StringValue') {
169 return `"""${defaultValue.value}""".trimIndent()`;
170 }
171 else if (defaultValue.kind === 'EnumValue') {
172 return `${typeName}.${defaultValue.value}`;
173 }
174 else if (defaultValue.kind === 'ListValue') {
175 const list = defaultValue.values
176 .map(value => {
177 return this.initialValue(typeName, value);
178 })
179 .join(', ');
180 return `listOf(${list})`;
181 }
182 // Variable
183 // ObjectValue
184 // ObjectField
185 }
186 return undefined;
187 }
188 FieldDefinition(node) {
189 if (node.arguments.length > 0) {
190 const inputTransformer = (typeName) => {
191 const transformerName = `${this.convertName(typeName, { useTypesPrefix: true })}${this.convertName(node.name.value, { useTypesPrefix: false })}Args`;
192 return this.buildInputTransfomer(transformerName, node.arguments);
193 };
194 return { node, inputTransformer };
195 }
196 return { node };
197 }
198 InputObjectTypeDefinition(node) {
199 const convertedName = this.convertName(node);
200 const name = convertedName.endsWith('Input') ? convertedName : `${convertedName}Input`;
201 return this.buildInputTransfomer(name, node.fields);
202 }
203 ObjectTypeDefinition(node) {
204 const name = this.convertName(node);
205 const fields = node.fields;
206 const fieldNodes = [];
207 const argsTypes = [];
208 fields.forEach(({ node, inputTransformer }) => {
209 if (node) {
210 fieldNodes.push(node);
211 }
212 if (inputTransformer) {
213 argsTypes.push(inputTransformer);
214 }
215 });
216 let types = argsTypes.map(f => f(node.name.value)).filter(r => r);
217 if (this.config.withTypes) {
218 types = types.concat([this.buildTypeTransfomer(name, fieldNodes)]);
219 }
220 return types.join('\n');
221 }
222}
223
224const plugin = async (schema, documents, config, { outputFile }) => {
225 const relevantPath = dirname(normalize(outputFile));
226 const defaultPackageName = buildPackageNameFromPath(relevantPath);
227 const visitor = new KotlinResolversVisitor(config, schema, defaultPackageName);
228 const astNode = getCachedDocumentNodeFromSchema(schema);
229 const visitorResult = visit(astNode, { leave: visitor });
230 const packageName = visitor.getPackageName();
231 const blockContent = visitorResult.definitions.filter(d => typeof d === 'string').join('\n\n');
232 return [packageName, blockContent].join('\n');
233};
234
235export { plugin };