1 | import {
|
2 | GraphQLType,
|
3 | GraphQLString,
|
4 | GraphQLInt,
|
5 | GraphQLFloat,
|
6 | GraphQLBoolean,
|
7 | GraphQLID,
|
8 | GraphQLScalarType,
|
9 | isCompositeType,
|
10 | getNamedType,
|
11 | GraphQLInputField,
|
12 | isNonNullType,
|
13 | isListType,
|
14 | isScalarType,
|
15 | isEnumType
|
16 | } from "graphql";
|
17 |
|
18 | import {
|
19 | camelCase as _camelCase,
|
20 | pascalCase as _pascalCase
|
21 | } from "change-case";
|
22 | import * as Inflector from "inflected";
|
23 | import { join, wrap } from "apollo-codegen-core/lib/utilities/printing";
|
24 |
|
25 | import { Property, Struct, SwiftSource, swift } from "./language";
|
26 |
|
27 | import {
|
28 | CompilerOptions,
|
29 | SelectionSet,
|
30 | Field,
|
31 | FragmentSpread,
|
32 | Argument
|
33 | } from "apollo-codegen-core/lib/compiler";
|
34 | import { isMetaFieldName } from "apollo-codegen-core/lib/utilities/graphql";
|
35 | import { Variant } from "apollo-codegen-core/lib/compiler/visitors/typeCase";
|
36 | import { collectAndMergeFields } from "apollo-codegen-core/lib/compiler/visitors/collectAndMergeFields";
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 | const builtInScalarMap = {
|
43 | [GraphQLString.name]: "String",
|
44 | [GraphQLInt.name]: "Int",
|
45 | [GraphQLFloat.name]: "Double",
|
46 | [GraphQLBoolean.name]: "Bool",
|
47 | [GraphQLID.name]: "GraphQLID"
|
48 | };
|
49 |
|
50 | export class Helpers {
|
51 | constructor(public options: CompilerOptions) {}
|
52 |
|
53 |
|
54 |
|
55 | typeNameFromGraphQLType(
|
56 | type: GraphQLType,
|
57 | unmodifiedTypeName?: string,
|
58 | isOptional?: boolean
|
59 | ): string {
|
60 | if (isNonNullType(type)) {
|
61 | return this.typeNameFromGraphQLType(
|
62 | type.ofType,
|
63 | unmodifiedTypeName,
|
64 | false
|
65 | );
|
66 | } else if (isOptional === undefined) {
|
67 | isOptional = true;
|
68 | }
|
69 |
|
70 | let typeName;
|
71 | if (isListType(type)) {
|
72 | typeName =
|
73 | "[" +
|
74 | this.typeNameFromGraphQLType(type.ofType, unmodifiedTypeName) +
|
75 | "]";
|
76 | } else if (isScalarType(type)) {
|
77 | typeName = this.typeNameForScalarType(type);
|
78 | } else {
|
79 | typeName = unmodifiedTypeName || type.name;
|
80 | }
|
81 |
|
82 | return isOptional ? typeName + "?" : typeName;
|
83 | }
|
84 |
|
85 | typeNameForScalarType(type: GraphQLScalarType): string {
|
86 | return (
|
87 | builtInScalarMap[type.name] ||
|
88 | (this.options.passthroughCustomScalars
|
89 | ? this.options.customScalarsPrefix + type.name
|
90 | : GraphQLString.name)
|
91 | );
|
92 | }
|
93 |
|
94 | fieldTypeEnum(type: GraphQLType, structName: string): SwiftSource {
|
95 | if (isNonNullType(type)) {
|
96 | return swift`.nonNull(${this.fieldTypeEnum(type.ofType, structName)})`;
|
97 | } else if (isListType(type)) {
|
98 | return swift`.list(${this.fieldTypeEnum(type.ofType, structName)})`;
|
99 | } else if (isScalarType(type)) {
|
100 | return swift`.scalar(${this.typeNameForScalarType(type)}.self)`;
|
101 | } else if (isEnumType(type)) {
|
102 | return swift`.scalar(${type.name}.self)`;
|
103 | } else if (isCompositeType(type)) {
|
104 | return swift`.object(${structName}.selections)`;
|
105 | } else {
|
106 | throw new Error(`Unknown field type: ${type}`);
|
107 | }
|
108 | }
|
109 |
|
110 |
|
111 |
|
112 | enumCaseName(name: string) {
|
113 | return camelCase(name);
|
114 | }
|
115 |
|
116 | enumDotCaseName(name: string): SwiftSource {
|
117 | return swift`.${SwiftSource.memberName(camelCase(name))}`;
|
118 | }
|
119 |
|
120 | operationClassName(name: string) {
|
121 | return pascalCase(name);
|
122 | }
|
123 |
|
124 | structNameForPropertyName(propertyName: string) {
|
125 | return pascalCase(Inflector.singularize(propertyName));
|
126 | }
|
127 |
|
128 | structNameForFragmentName(fragmentName: string) {
|
129 | return pascalCase(fragmentName);
|
130 | }
|
131 |
|
132 | structNameForVariant(variant: SelectionSet) {
|
133 | return (
|
134 | "As" + variant.possibleTypes.map(type => pascalCase(type.name)).join("Or")
|
135 | );
|
136 | }
|
137 |
|
138 | |
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 | internalParameterName(
|
148 | propertyName: string,
|
149 | properties: { propertyName: string }[]
|
150 | ): string {
|
151 | return SwiftSource.isValidParameterName(propertyName)
|
152 | ? propertyName
|
153 | : makeUniqueName(`_${propertyName}`, properties);
|
154 | }
|
155 |
|
156 |
|
157 |
|
158 | propertyFromField(
|
159 | field: Field,
|
160 | namespace?: string
|
161 | ): Field & Property & Struct {
|
162 | const { responseKey, isConditional } = field;
|
163 |
|
164 | const propertyName = isMetaFieldName(responseKey)
|
165 | ? responseKey
|
166 | : camelCase(responseKey);
|
167 |
|
168 | const structName = join(
|
169 | [namespace, this.structNameForPropertyName(responseKey)],
|
170 | "."
|
171 | );
|
172 |
|
173 | let type = field.type;
|
174 |
|
175 | if (isConditional && isNonNullType(type)) {
|
176 | type = type.ofType;
|
177 | }
|
178 |
|
179 | const isOptional = !isNonNullType(type);
|
180 |
|
181 | const unmodifiedType = getNamedType(field.type);
|
182 |
|
183 | const unmodifiedTypeName = isCompositeType(unmodifiedType)
|
184 | ? structName
|
185 | : unmodifiedType.name;
|
186 |
|
187 | const typeName = this.typeNameFromGraphQLType(type, unmodifiedTypeName);
|
188 |
|
189 | return Object.assign({}, field, {
|
190 | responseKey,
|
191 | propertyName,
|
192 | typeName,
|
193 | structName,
|
194 | isOptional
|
195 | });
|
196 | }
|
197 |
|
198 | propertyFromVariant(variant: Variant): Variant & Property & Struct {
|
199 | const structName = this.structNameForVariant(variant);
|
200 |
|
201 | return Object.assign(variant, {
|
202 | propertyName: camelCase(structName),
|
203 | typeName: structName + "?",
|
204 | structName
|
205 | });
|
206 | }
|
207 |
|
208 | propertyFromFragmentSpread(
|
209 | fragmentSpread: FragmentSpread,
|
210 | isConditional: boolean
|
211 | ): FragmentSpread & Property & Struct {
|
212 | const structName = this.structNameForFragmentName(
|
213 | fragmentSpread.fragmentName
|
214 | );
|
215 |
|
216 | return Object.assign({}, fragmentSpread, {
|
217 | propertyName: camelCase(fragmentSpread.fragmentName),
|
218 | typeName: isConditional ? structName + "?" : structName,
|
219 | structName,
|
220 | isConditional
|
221 | });
|
222 | }
|
223 |
|
224 | propertyFromInputField(field: GraphQLInputField) {
|
225 | return Object.assign({}, field, {
|
226 | propertyName: camelCase(field.name),
|
227 | typeName: this.typeNameFromGraphQLType(field.type),
|
228 | isOptional: !isNonNullType(field.type)
|
229 | });
|
230 | }
|
231 |
|
232 | propertiesForSelectionSet(
|
233 | selectionSet: SelectionSet,
|
234 | namespace?: string
|
235 | ): (Field & Property & Struct)[] | undefined {
|
236 | const properties = collectAndMergeFields(selectionSet, true)
|
237 | .filter(field => field.name !== "__typename")
|
238 | .map(field => this.propertyFromField(field, namespace));
|
239 |
|
240 |
|
241 |
|
242 | if (
|
243 | selectionSet.selections.some(
|
244 | selection => selection.kind === "FragmentSpread"
|
245 | ) &&
|
246 | properties.some(property => isCompositeType(getNamedType(property.type)))
|
247 | ) {
|
248 | return undefined;
|
249 | }
|
250 |
|
251 | return properties;
|
252 | }
|
253 |
|
254 |
|
255 |
|
256 | dictionaryLiteralForFieldArguments(args: Argument[]): SwiftSource {
|
257 | function expressionFromValue(value: any): SwiftSource {
|
258 | if (value === null) {
|
259 | return swift`nil`;
|
260 | } else if (value.kind === "Variable") {
|
261 | return swift`GraphQLVariable(${SwiftSource.string(
|
262 | value.variableName
|
263 | )})`;
|
264 | } else if (Array.isArray(value)) {
|
265 | return (
|
266 | SwiftSource.wrap(
|
267 | swift`[`,
|
268 | SwiftSource.join(value.map(expressionFromValue), ", "),
|
269 | swift`]`
|
270 | ) || swift`[]`
|
271 | );
|
272 | } else if (typeof value === "object") {
|
273 | return (
|
274 | SwiftSource.wrap(
|
275 | swift`[`,
|
276 | SwiftSource.join(
|
277 | Object.entries(value).map(([key, value]) => {
|
278 | return swift`${SwiftSource.string(key)}: ${expressionFromValue(
|
279 | value
|
280 | )}`;
|
281 | }),
|
282 | ", "
|
283 | ),
|
284 | swift`]`
|
285 | ) || swift`[:]`
|
286 | );
|
287 | } else if (typeof value === "string") {
|
288 | return SwiftSource.string(value);
|
289 | } else {
|
290 | return new SwiftSource(JSON.stringify(value));
|
291 | }
|
292 | }
|
293 |
|
294 | return (
|
295 | SwiftSource.wrap(
|
296 | swift`[`,
|
297 | SwiftSource.join(
|
298 | args.map(arg => {
|
299 | return swift`${SwiftSource.string(arg.name)}: ${expressionFromValue(
|
300 | arg.value
|
301 | )}`;
|
302 | }),
|
303 | ", "
|
304 | ),
|
305 | swift`]`
|
306 | ) || swift`[:]`
|
307 | );
|
308 | }
|
309 |
|
310 | mapExpressionForType(
|
311 | type: GraphQLType,
|
312 | isConditional: boolean = false,
|
313 | makeExpression: (expression: SwiftSource) => SwiftSource,
|
314 | expression: SwiftSource,
|
315 | inputTypeName: string,
|
316 | outputTypeName: string
|
317 | ): SwiftSource {
|
318 | let isOptional;
|
319 | if (isNonNullType(type)) {
|
320 | isOptional = !!isConditional;
|
321 | type = type.ofType;
|
322 | } else {
|
323 | isOptional = true;
|
324 | }
|
325 |
|
326 | if (isListType(type)) {
|
327 | const elementType = type.ofType;
|
328 | if (isOptional) {
|
329 | return swift`${expression}.flatMap { ${makeClosureSignature(
|
330 | this.typeNameFromGraphQLType(type, inputTypeName, false),
|
331 | this.typeNameFromGraphQLType(type, outputTypeName, false)
|
332 | )} value.map { ${makeClosureSignature(
|
333 | this.typeNameFromGraphQLType(elementType, inputTypeName),
|
334 | this.typeNameFromGraphQLType(elementType, outputTypeName)
|
335 | )} ${this.mapExpressionForType(
|
336 | elementType,
|
337 | undefined,
|
338 | makeExpression,
|
339 | swift`value`,
|
340 | inputTypeName,
|
341 | outputTypeName
|
342 | )} } }`;
|
343 | } else {
|
344 | return swift`${expression}.map { ${makeClosureSignature(
|
345 | this.typeNameFromGraphQLType(elementType, inputTypeName),
|
346 | this.typeNameFromGraphQLType(elementType, outputTypeName)
|
347 | )} ${this.mapExpressionForType(
|
348 | elementType,
|
349 | undefined,
|
350 | makeExpression,
|
351 | swift`value`,
|
352 | inputTypeName,
|
353 | outputTypeName
|
354 | )} }`;
|
355 | }
|
356 | } else if (isOptional) {
|
357 | return swift`${expression}.flatMap { ${makeClosureSignature(
|
358 | this.typeNameFromGraphQLType(type, inputTypeName, false),
|
359 | this.typeNameFromGraphQLType(type, outputTypeName, false)
|
360 | )} ${makeExpression(swift`value`)} }`;
|
361 | } else {
|
362 | return makeExpression(expression);
|
363 | }
|
364 | }
|
365 | }
|
366 |
|
367 | function makeClosureSignature(
|
368 | parameterTypeName: string,
|
369 | returnTypeName?: string
|
370 | ): SwiftSource {
|
371 | let closureSignature = swift`(value: ${parameterTypeName})`;
|
372 |
|
373 | if (returnTypeName) {
|
374 | closureSignature.append(swift` -> ${returnTypeName}`);
|
375 | }
|
376 | closureSignature.append(swift` in`);
|
377 | return closureSignature;
|
378 | }
|
379 |
|
380 |
|
381 |
|
382 |
|
383 |
|
384 |
|
385 |
|
386 | function makeUniqueName(
|
387 | proposedName: string,
|
388 | properties: { propertyName: string }[]
|
389 | ): string {
|
390 |
|
391 |
|
392 | for (let name = proposedName; ; name += "_") {
|
393 | if (properties.every(prop => prop.propertyName != name)) {
|
394 | return name;
|
395 | }
|
396 | }
|
397 | }
|
398 |
|
399 |
|
400 |
|
401 |
|
402 |
|
403 |
|
404 | function camelCase(value: string): string {
|
405 | const [_, prefix, middle, suffix] = value.match(/^(_*)(.*?)(_*)$/) || [
|
406 | "",
|
407 | "",
|
408 | value,
|
409 | ""
|
410 | ];
|
411 | return `${prefix}${_camelCase(middle)}${suffix}`;
|
412 | }
|
413 |
|
414 |
|
415 |
|
416 |
|
417 |
|
418 |
|
419 | function pascalCase(value: string): string {
|
420 | const [_, prefix, middle, suffix] = value.match(/^(_*)(.*?)(_*)$/) || [
|
421 | "",
|
422 | "",
|
423 | value,
|
424 | ""
|
425 | ];
|
426 | return `${prefix}${_pascalCase(middle)}${suffix}`;
|
427 | }
|