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" +
|
135 | variant.possibleTypes.map((type) => pascalCase(type.name)).join("Or")
|
136 | );
|
137 | }
|
138 |
|
139 | |
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 | internalParameterName(
|
149 | propertyName: string,
|
150 | properties: { propertyName: string }[]
|
151 | ): string {
|
152 | return SwiftSource.isValidParameterName(propertyName)
|
153 | ? propertyName
|
154 | : makeUniqueName(`_${propertyName}`, properties);
|
155 | }
|
156 |
|
157 |
|
158 |
|
159 | propertyFromField(
|
160 | field: Field,
|
161 | namespace?: string
|
162 | ): Field & Property & Struct {
|
163 | const { responseKey, isConditional } = field;
|
164 |
|
165 | const propertyName = isMetaFieldName(responseKey)
|
166 | ? responseKey
|
167 | : camelCase(responseKey);
|
168 |
|
169 | const structName = join(
|
170 | [namespace, this.structNameForPropertyName(responseKey)],
|
171 | "."
|
172 | );
|
173 |
|
174 | let type = field.type;
|
175 |
|
176 | if (isConditional && isNonNullType(type)) {
|
177 | type = type.ofType;
|
178 | }
|
179 |
|
180 | const isOptional = !isNonNullType(type);
|
181 |
|
182 | const unmodifiedType = getNamedType(field.type);
|
183 |
|
184 | const unmodifiedTypeName = isCompositeType(unmodifiedType)
|
185 | ? structName
|
186 | : unmodifiedType.name;
|
187 |
|
188 | const typeName = this.typeNameFromGraphQLType(type, unmodifiedTypeName);
|
189 |
|
190 | return Object.assign({}, field, {
|
191 | responseKey,
|
192 | propertyName,
|
193 | typeName,
|
194 | structName,
|
195 | isOptional,
|
196 | });
|
197 | }
|
198 |
|
199 | propertyFromVariant(variant: Variant): Variant & Property & Struct {
|
200 | const structName = this.structNameForVariant(variant);
|
201 |
|
202 | return Object.assign(variant, {
|
203 | propertyName: camelCase(structName),
|
204 | typeName: structName + "?",
|
205 | structName,
|
206 | });
|
207 | }
|
208 |
|
209 | propertyFromFragmentSpread(
|
210 | fragmentSpread: FragmentSpread,
|
211 | isConditional: boolean
|
212 | ): FragmentSpread & Property & Struct {
|
213 | const structName = this.structNameForFragmentName(
|
214 | fragmentSpread.fragmentName
|
215 | );
|
216 |
|
217 | return Object.assign({}, fragmentSpread, {
|
218 | propertyName: camelCase(fragmentSpread.fragmentName),
|
219 | typeName: isConditional ? structName + "?" : structName,
|
220 | structName,
|
221 | isConditional,
|
222 | });
|
223 | }
|
224 |
|
225 | propertyFromInputField(field: GraphQLInputField) {
|
226 | return Object.assign({}, field, {
|
227 | propertyName: camelCase(field.name),
|
228 | typeName: this.typeNameFromGraphQLType(field.type),
|
229 | isOptional: !isNonNullType(field.type),
|
230 | });
|
231 | }
|
232 |
|
233 | propertiesForSelectionSet(
|
234 | selectionSet: SelectionSet,
|
235 | namespace?: string
|
236 | ): (Field & Property & Struct)[] | undefined {
|
237 | const properties = collectAndMergeFields(selectionSet, true)
|
238 | .filter((field) => field.name !== "__typename")
|
239 | .map((field) => this.propertyFromField(field, namespace));
|
240 |
|
241 |
|
242 |
|
243 | if (
|
244 | selectionSet.selections.some(
|
245 | (selection) => selection.kind === "FragmentSpread"
|
246 | ) &&
|
247 | properties.some((property) =>
|
248 | isCompositeType(getNamedType(property.type))
|
249 | )
|
250 | ) {
|
251 | return undefined;
|
252 | }
|
253 |
|
254 | return properties;
|
255 | }
|
256 |
|
257 |
|
258 |
|
259 | dictionaryLiteralForFieldArguments(args: Argument[]): SwiftSource {
|
260 | function expressionFromValue(value: any): SwiftSource {
|
261 | if (value === null) {
|
262 | return swift`nil`;
|
263 | } else if (value.kind === "Variable") {
|
264 | return swift`GraphQLVariable(${SwiftSource.string(
|
265 | value.variableName
|
266 | )})`;
|
267 | } else if (Array.isArray(value)) {
|
268 | return (
|
269 | SwiftSource.wrap(
|
270 | swift`[`,
|
271 | SwiftSource.join(value.map(expressionFromValue), ", "),
|
272 | swift`]`
|
273 | ) || swift`[]`
|
274 | );
|
275 | } else if (typeof value === "object") {
|
276 | return (
|
277 | SwiftSource.wrap(
|
278 | swift`[`,
|
279 | SwiftSource.join(
|
280 | Object.entries(value).map(([key, value]) => {
|
281 | return swift`${SwiftSource.string(key)}: ${expressionFromValue(
|
282 | value
|
283 | )}`;
|
284 | }),
|
285 | ", "
|
286 | ),
|
287 | swift`]`
|
288 | ) || swift`[:]`
|
289 | );
|
290 | } else if (typeof value === "string") {
|
291 | return SwiftSource.string(value);
|
292 | } else {
|
293 | return new SwiftSource(JSON.stringify(value));
|
294 | }
|
295 | }
|
296 |
|
297 | return (
|
298 | SwiftSource.wrap(
|
299 | swift`[`,
|
300 | SwiftSource.join(
|
301 | args.map((arg) => {
|
302 | return swift`${SwiftSource.string(arg.name)}: ${expressionFromValue(
|
303 | arg.value
|
304 | )}`;
|
305 | }),
|
306 | ", "
|
307 | ),
|
308 | swift`]`
|
309 | ) || swift`[:]`
|
310 | );
|
311 | }
|
312 |
|
313 | mapExpressionForType(
|
314 | type: GraphQLType,
|
315 | isConditional: boolean = false,
|
316 | makeExpression: (expression: SwiftSource) => SwiftSource,
|
317 | expression: SwiftSource,
|
318 | inputTypeName: string,
|
319 | outputTypeName: string
|
320 | ): SwiftSource {
|
321 | let isOptional;
|
322 | if (isNonNullType(type)) {
|
323 | isOptional = !!isConditional;
|
324 | type = type.ofType;
|
325 | } else {
|
326 | isOptional = true;
|
327 | }
|
328 |
|
329 | if (isListType(type)) {
|
330 | const elementType = type.ofType;
|
331 | if (isOptional) {
|
332 | return swift`${expression}.flatMap { ${makeClosureSignature(
|
333 | this.typeNameFromGraphQLType(type, inputTypeName, false),
|
334 | this.typeNameFromGraphQLType(type, outputTypeName, false)
|
335 | )} value.map { ${makeClosureSignature(
|
336 | this.typeNameFromGraphQLType(elementType, inputTypeName),
|
337 | this.typeNameFromGraphQLType(elementType, outputTypeName)
|
338 | )} ${this.mapExpressionForType(
|
339 | elementType,
|
340 | undefined,
|
341 | makeExpression,
|
342 | swift`value`,
|
343 | inputTypeName,
|
344 | outputTypeName
|
345 | )} } }`;
|
346 | } else {
|
347 | return swift`${expression}.map { ${makeClosureSignature(
|
348 | this.typeNameFromGraphQLType(elementType, inputTypeName),
|
349 | this.typeNameFromGraphQLType(elementType, outputTypeName)
|
350 | )} ${this.mapExpressionForType(
|
351 | elementType,
|
352 | undefined,
|
353 | makeExpression,
|
354 | swift`value`,
|
355 | inputTypeName,
|
356 | outputTypeName
|
357 | )} }`;
|
358 | }
|
359 | } else if (isOptional) {
|
360 | return swift`${expression}.flatMap { ${makeClosureSignature(
|
361 | this.typeNameFromGraphQLType(type, inputTypeName, false),
|
362 | this.typeNameFromGraphQLType(type, outputTypeName, false)
|
363 | )} ${makeExpression(swift`value`)} }`;
|
364 | } else {
|
365 | return makeExpression(expression);
|
366 | }
|
367 | }
|
368 | }
|
369 |
|
370 | function makeClosureSignature(
|
371 | parameterTypeName: string,
|
372 | returnTypeName?: string
|
373 | ): SwiftSource {
|
374 | let closureSignature = swift`(value: ${parameterTypeName})`;
|
375 |
|
376 | if (returnTypeName) {
|
377 | closureSignature.append(swift` -> ${returnTypeName}`);
|
378 | }
|
379 | closureSignature.append(swift` in`);
|
380 | return closureSignature;
|
381 | }
|
382 |
|
383 |
|
384 |
|
385 |
|
386 |
|
387 |
|
388 |
|
389 | function makeUniqueName(
|
390 | proposedName: string,
|
391 | properties: { propertyName: string }[]
|
392 | ): string {
|
393 |
|
394 |
|
395 | for (let name = proposedName; ; name += "_") {
|
396 | if (properties.every((prop) => prop.propertyName != name)) {
|
397 | return name;
|
398 | }
|
399 | }
|
400 | }
|
401 |
|
402 |
|
403 |
|
404 |
|
405 |
|
406 |
|
407 | function camelCase(value: string): string {
|
408 | const [_, prefix, middle, suffix] = value.match(/^(_*)(.*?)(_*)$/) || [
|
409 | "",
|
410 | "",
|
411 | value,
|
412 | "",
|
413 | ];
|
414 | return `${prefix}${_camelCase(middle)}${suffix}`;
|
415 | }
|
416 |
|
417 |
|
418 |
|
419 |
|
420 |
|
421 |
|
422 | function pascalCase(value: string): string {
|
423 | const [_, prefix, middle, suffix] = value.match(/^(_*)(.*?)(_*)$/) || [
|
424 | "",
|
425 | "",
|
426 | value,
|
427 | "",
|
428 | ];
|
429 | return `${prefix}${_pascalCase(middle)}${suffix}`;
|
430 | }
|