HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/SBogers10/shop.komma.nl/node_modules/apollo-codegen-swift/src/codeGeneration.ts
import path from "path";

import {
  GraphQLError,
  GraphQLType,
  getNamedType,
  isCompositeType,
  GraphQLEnumType,
  GraphQLInputObjectType,
  isNonNullType,
  isListType,
  isEnumType,
  isInputObjectType
} from "graphql";

import {
  CompilerContext,
  Operation,
  Fragment,
  SelectionSet,
  Field
} from "apollo-codegen-core/lib/compiler";

import {
  SwiftGenerator,
  Property,
  Struct,
  SwiftSource,
  swift
} from "./language";
import { Helpers } from "./helpers";
import { isList } from "apollo-codegen-core/lib/utilities/graphql";

import {
  typeCaseForSelectionSet,
  TypeCase,
  Variant
} from "apollo-codegen-core/lib/compiler/visitors/typeCase";
import { collectFragmentsReferenced } from "apollo-codegen-core/lib/compiler/visitors/collectFragmentsReferenced";
import { generateOperationId } from "apollo-codegen-core/lib/compiler/visitors/generateOperationId";
import { collectAndMergeFields } from "apollo-codegen-core/lib/compiler/visitors/collectAndMergeFields";

import "apollo-codegen-core/lib/utilities/array";

const { join, wrap } = SwiftSource;

export interface Options {
  namespace?: string;
  passthroughCustomScalars?: boolean;
  customScalarsPrefix?: string;
}

/**
 * The main method to call from outside this package to generate Swift code.
 *
 * @param context The `CompilerContext` to use to generate code.
 * @param outputIndividualFiles Generates individual files per query/fragment if true,
 *                              otherwise shoves everything into one giant file.
 * @param only [optional] The path to a file which is the only file which should be regenerated.
 *             If absent, all files will be regenerated.
 */
export function generateSource(
  context: CompilerContext,
  outputIndividualFiles: boolean,
  suppressMultilineStringLiterals: boolean,
  only?: string
): SwiftAPIGenerator {
  const generator = new SwiftAPIGenerator(context);

  if (outputIndividualFiles) {
    generator.withinFile(`Types.graphql.swift`, () => {
      generator.fileHeader();

      generator.namespaceDeclaration(context.options.namespace, () => {
        context.typesUsed.forEach(type => {
          generator.typeDeclarationForGraphQLType(type, true);
        });
      });
    });

    const inputFilePaths = new Set<string>();

    Object.values(context.operations).forEach(operation => {
      inputFilePaths.add(operation.filePath);
    });

    Object.values(context.fragments).forEach(fragment => {
      inputFilePaths.add(fragment.filePath);
    });

    for (const inputFilePath of inputFilePaths) {
      if (only && inputFilePath !== only) continue;

      generator.withinFile(`${path.basename(inputFilePath)}.swift`, () => {
        generator.fileHeader();

        generator.namespaceExtensionDeclaration(
          context.options.namespace,
          () => {
            Object.values(context.operations).forEach(operation => {
              if (operation.filePath === inputFilePath) {
                generator.classDeclarationForOperation(
                  operation,
                  true,
                  suppressMultilineStringLiterals
                );
              }
            });

            Object.values(context.fragments).forEach(fragment => {
              if (fragment.filePath === inputFilePath) {
                generator.structDeclarationForFragment(
                  fragment,
                  true,
                  suppressMultilineStringLiterals
                );
              }
            });
          }
        );
      });
    }
  } else {
    generator.fileHeader();

    generator.namespaceDeclaration(context.options.namespace, () => {
      context.typesUsed.forEach(type => {
        generator.typeDeclarationForGraphQLType(type, false);
      });

      Object.values(context.operations).forEach(operation => {
        generator.classDeclarationForOperation(
          operation,
          false,
          suppressMultilineStringLiterals
        );
      });

      Object.values(context.fragments).forEach(fragment => {
        generator.structDeclarationForFragment(
          fragment,
          false,
          suppressMultilineStringLiterals
        );
      });
    });
  }

  return generator;
}

export class SwiftAPIGenerator extends SwiftGenerator<CompilerContext> {
  helpers: Helpers;

  constructor(context: CompilerContext) {
    super(context);

    this.helpers = new Helpers(context.options);
  }

  fileHeader() {
    this.printOnNewline(SwiftSource.raw`// @generated`);
    this.printOnNewline(
      SwiftSource.raw`//  This file was automatically generated and should not be edited.`
    );
    this.printNewline();
    this.printOnNewline(swift`import Apollo`);
    this.printOnNewline(swift`import Foundation`);
  }

  /**
   * Generates the class declaration for an operation.
   *
   * @param operation The operaton to generate the class declaration for.
   * @param outputIndividualFiles If this operation is being output as individual files, to help prevent
   *                              redundant usages of the `public` modifier in enum extensions.
   */
  classDeclarationForOperation(
    operation: Operation,
    outputIndividualFiles: boolean,
    suppressMultilineStringLiterals: boolean
  ) {
    const {
      operationName,
      operationType,
      variables,
      source,
      selectionSet
    } = operation;

    let className;
    let protocol;

    switch (operationType) {
      case "query":
        className = `${this.helpers.operationClassName(operationName)}Query`;
        protocol = "GraphQLQuery";
        break;
      case "mutation":
        className = `${this.helpers.operationClassName(operationName)}Mutation`;
        protocol = "GraphQLMutation";
        break;
      case "subscription":
        className = `${this.helpers.operationClassName(
          operationName
        )}Subscription`;
        protocol = "GraphQLSubscription";
        break;
      default:
        throw new GraphQLError(`Unsupported operation type "${operationType}"`);
    }

    const {
      options: { namespace },
      fragments
    } = this.context;
    const isRedundant = !!namespace && outputIndividualFiles;
    const modifiers = isRedundant ? ["final"] : ["public", "final"];

    this.classDeclaration(
      {
        className,
        modifiers,
        adoptedProtocols: [protocol]
      },
      () => {
        if (source) {
          this.comment("The raw GraphQL definition of this operation.");
          this.printOnNewline(swift`public let operationDefinition: String =`);
          this.withIndent(() => {
            this.multilineString(source, suppressMultilineStringLiterals);
          });
        }

        this.printNewlineIfNeeded();
        this.printOnNewline(
          swift`public let operationName: String = ${SwiftSource.string(
            operationName
          )}`
        );

        const fragmentsReferenced = collectFragmentsReferenced(
          operation.selectionSet,
          fragments
        );

        if (this.context.options.generateOperationIds) {
          const { operationId, sourceWithFragments } = generateOperationId(
            operation,
            fragments,
            fragmentsReferenced
          );
          operation.operationId = operationId;
          operation.sourceWithFragments = sourceWithFragments;
          this.printNewlineIfNeeded();
          this.printOnNewline(
            swift`public let operationIdentifier: String? = ${SwiftSource.string(
              operationId
            )}`
          );
        }

        if (fragmentsReferenced.size > 0) {
          this.printNewlineIfNeeded();
          this.printOnNewline(
            swift`public var queryDocument: String { return operationDefinition`
          );
          fragmentsReferenced.forEach(fragmentName => {
            this.print(
              swift`.appending("\\n" + ${this.helpers.structNameForFragmentName(
                fragmentName
              )}.fragmentDefinition)`
            );
          });
          this.print(swift` }`);
        }

        this.printNewlineIfNeeded();

        if (variables && variables.length > 0) {
          const properties = variables.map(({ name, type }) => {
            const typeName = this.helpers.typeNameFromGraphQLType(type);
            const isOptional = !(
              isNonNullType(type) ||
              (isListType(type) && isNonNullType(type.ofType))
            );
            return { name, propertyName: name, type, typeName, isOptional };
          });

          this.propertyDeclarations(properties);

          this.printNewlineIfNeeded();
          this.initializerDeclarationForProperties(properties);

          this.printNewlineIfNeeded();
          this.printOnNewline(swift`public var variables: GraphQLMap?`);
          this.withinBlock(() => {
            this.printOnNewline(
              wrap(
                swift`return [`,
                join(
                  properties.map(
                    ({ name, propertyName }) =>
                      swift`${SwiftSource.string(name)}: ${propertyName}`
                  ),
                  ", "
                ) || swift`:`,
                swift`]`
              )
            );
          });
        } else {
          this.initializerDeclarationForProperties([]);
        }

        this.structDeclarationForSelectionSet(
          {
            structName: "Data",
            selectionSet
          },
          outputIndividualFiles
        );
      }
    );
  }

  /**
   * Generates the struct declaration for a fragment.
   *
   * @param param0 The fragment name, selectionSet, and source to use to generate the struct
   * @param outputIndividualFiles If this operation is being output as individual files, to help prevent
   *                              redundant usages of the `public` modifier in enum extensions.
   */
  structDeclarationForFragment(
    { fragmentName, selectionSet, source }: Fragment,
    outputIndividualFiles: boolean,
    suppressMultilineStringLiterals: boolean
  ) {
    const structName = this.helpers.structNameForFragmentName(fragmentName);

    this.structDeclarationForSelectionSet(
      {
        structName,
        adoptedProtocols: ["GraphQLFragment"],
        selectionSet
      },
      outputIndividualFiles,
      () => {
        if (source) {
          this.comment("The raw GraphQL definition of this fragment.");
          this.printOnNewline(
            swift`public static let fragmentDefinition: String =`
          );
          this.withIndent(() => {
            this.multilineString(source, suppressMultilineStringLiterals);
          });
        }
      }
    );
  }

  /**
   * Generates the struct declaration for a selection set.
   *
   * @param param0 The name, adoptedProtocols, and selectionSet to use to generate the struct
   * @param outputIndividualFiles If this operation is being output as individual files, to help prevent
   *                              redundant usages of the `public` modifier in enum extensions.
   * @param before [optional] A function to execute before generating the struct declaration.
   */
  structDeclarationForSelectionSet(
    {
      structName,
      adoptedProtocols = ["GraphQLSelectionSet"],
      selectionSet
    }: {
      structName: string;
      adoptedProtocols?: string[];
      selectionSet: SelectionSet;
    },
    outputIndividualFiles: boolean,
    before?: Function
  ) {
    const typeCase = typeCaseForSelectionSet(
      selectionSet,
      !!this.context.options.mergeInFieldsFromFragmentSpreads
    );

    this.structDeclarationForVariant(
      {
        structName,
        adoptedProtocols,
        variant: typeCase.default,
        typeCase
      },
      outputIndividualFiles,
      before,
      () => {
        const variants = typeCase.variants.map(
          this.helpers.propertyFromVariant,
          this.helpers
        );

        for (const variant of variants) {
          this.propertyDeclarationForVariant(variant);

          this.structDeclarationForVariant(
            {
              structName: variant.structName,
              variant
            },
            outputIndividualFiles
          );
        }
      }
    );
  }

  /**
   * Generates the struct declaration for a variant
   *
   * @param param0 The structName, adoptedProtocols, variant, and typeCase to use to generate the struct
   * @param outputIndividualFiles If this operation is being output as individual files, to help prevent
   *                              redundant usages of the `public` modifier in enum extensions.
   * @param before [optional] A function to execute before generating the struct declaration.
   * @param after [optional] A function to execute after generating the struct declaration.
   */
  structDeclarationForVariant(
    {
      structName,
      adoptedProtocols = ["GraphQLSelectionSet"],
      variant,
      typeCase
    }: {
      structName: string;
      adoptedProtocols?: string[];
      variant: Variant;
      typeCase?: TypeCase;
    },
    outputIndividualFiles: boolean,
    before?: Function,
    after?: Function
  ) {
    const {
      options: {
        namespace,
        mergeInFieldsFromFragmentSpreads,
        omitDeprecatedEnumCases
      }
    } = this.context;

    this.structDeclaration(
      { structName, adoptedProtocols, namespace },
      outputIndividualFiles,
      () => {
        if (before) {
          before();
        }

        this.printNewlineIfNeeded();
        this.printOnNewline(
          swift`public static let possibleTypes: [String] = [`
        );
        this.print(
          join(
            variant.possibleTypes.map(
              type => swift`${SwiftSource.string(type.name)}`
            ),
            ", "
          )
        );
        this.print(swift`]`);

        this.printNewlineIfNeeded();
        this.printOnNewline(
          swift`public static var selections: [GraphQLSelection] {`
        );
        this.withIndent(() => {
          this.printOnNewline(swift`return `);
          if (typeCase) {
            this.typeCaseInitialization(typeCase);
          } else {
            this.selectionSetInitialization(variant);
          }
        });
        this.printOnNewline(swift`}`);
        this.printNewlineIfNeeded();

        this.printOnNewline(
          swift`public private(set) var resultMap: ResultMap`
        );

        this.printNewlineIfNeeded();
        this.printOnNewline(swift`public init(unsafeResultMap: ResultMap)`);
        this.withinBlock(() => {
          this.printOnNewline(swift`self.resultMap = unsafeResultMap`);
        });

        if (typeCase) {
          this.initializersForTypeCase(typeCase);
        } else {
          this.initializersForVariant(variant);
        }

        const fields = collectAndMergeFields(
          variant,
          !!mergeInFieldsFromFragmentSpreads
        ).map(field => this.helpers.propertyFromField(field as Field));

        const fragmentSpreads = variant.fragmentSpreads.map(fragmentSpread => {
          const isConditional = variant.possibleTypes.some(
            type => !fragmentSpread.selectionSet.possibleTypes.includes(type)
          );

          return this.helpers.propertyFromFragmentSpread(
            fragmentSpread,
            isConditional
          );
        });

        fields.forEach(this.propertyDeclarationForField, this);

        if (fragmentSpreads.length > 0) {
          this.printNewlineIfNeeded();
          this.printOnNewline(swift`public var fragments: Fragments`);
          this.withinBlock(() => {
            this.printOnNewline(swift`get`);
            this.withinBlock(() => {
              this.printOnNewline(
                swift`return Fragments(unsafeResultMap: resultMap)`
              );
            });
            this.printOnNewline(swift`set`);
            this.withinBlock(() => {
              this.printOnNewline(swift`resultMap += newValue.resultMap`);
            });
          });

          this.structDeclaration(
            {
              structName: "Fragments"
            },
            outputIndividualFiles,
            () => {
              this.printOnNewline(
                swift`public private(set) var resultMap: ResultMap`
              );

              this.printNewlineIfNeeded();
              this.printOnNewline(
                swift`public init(unsafeResultMap: ResultMap)`
              );
              this.withinBlock(() => {
                this.printOnNewline(swift`self.resultMap = unsafeResultMap`);
              });

              for (const fragmentSpread of fragmentSpreads) {
                const {
                  propertyName,
                  typeName,
                  structName,
                  isConditional
                } = fragmentSpread;

                this.printNewlineIfNeeded();
                this.printOnNewline(
                  swift`public var ${propertyName}: ${typeName}`
                );
                this.withinBlock(() => {
                  this.printOnNewline(swift`get`);
                  this.withinBlock(() => {
                    if (isConditional) {
                      this.printOnNewline(
                        swift`if !${structName}.possibleTypes.contains(resultMap["__typename"]! as! String) { return nil }`
                      );
                    }
                    this.printOnNewline(
                      swift`return ${structName}(unsafeResultMap: resultMap)`
                    );
                  });
                  this.printOnNewline(swift`set`);
                  this.withinBlock(() => {
                    if (isConditional) {
                      this.printOnNewline(
                        swift`guard let newValue = newValue else { return }`
                      );
                      this.printOnNewline(
                        swift`resultMap += newValue.resultMap`
                      );
                    } else {
                      this.printOnNewline(
                        swift`resultMap += newValue.resultMap`
                      );
                    }
                  });
                });
              }
            }
          );
        }

        for (const field of fields) {
          if (isCompositeType(getNamedType(field.type)) && field.selectionSet) {
            this.structDeclarationForSelectionSet(
              {
                structName: field.structName,
                selectionSet: field.selectionSet
              },
              outputIndividualFiles
            );
          }
        }

        if (after) {
          after();
        }
      }
    );
  }

  initializersForTypeCase(typeCase: TypeCase) {
    const variants = typeCase.variants;

    if (variants.length == 0) {
      this.initializersForVariant(typeCase.default);
    } else {
      const remainder = typeCase.remainder;
      for (const variant of remainder ? [remainder, ...variants] : variants) {
        this.initializersForVariant(
          variant,
          variant === remainder
            ? undefined
            : this.helpers.structNameForVariant(variant),
          false
        );
      }
    }
  }

  initializersForVariant(
    variant: Variant,
    namespace?: string,
    useInitializerIfPossible: boolean = true
  ) {
    if (useInitializerIfPossible && variant.possibleTypes.length == 1) {
      const properties = this.helpers.propertiesForSelectionSet(variant);
      if (!properties) return;

      this.printNewlineIfNeeded();
      this.printOnNewline(swift`public init`);

      this.parametersForProperties(properties);

      this.withinBlock(() => {
        this.printOnNewline(
          wrap(
            swift`self.init(unsafeResultMap: [`,
            join(
              [
                swift`"__typename": ${SwiftSource.string(
                  variant.possibleTypes[0].toString()
                )}`,
                ...properties.map(p =>
                  this.propertyAssignmentForField(p, properties)
                )
              ],
              ", "
            ) || swift`:`,
            swift`])`
          )
        );
      });
    } else {
      const structName = this.scope.typeName;

      for (const possibleType of variant.possibleTypes) {
        const properties = this.helpers.propertiesForSelectionSet(
          {
            possibleTypes: [possibleType],
            selections: variant.selections
          },
          namespace
        );

        if (!properties) continue;

        this.printNewlineIfNeeded();
        this.printOnNewline(
          SwiftSource.raw`public static func make${possibleType}`
        );

        this.parametersForProperties(properties);

        this.print(swift` -> ${structName}`);

        this.withinBlock(() => {
          this.printOnNewline(
            wrap(
              swift`return ${structName}(unsafeResultMap: [`,
              join(
                [
                  swift`"__typename": ${SwiftSource.string(
                    possibleType.toString()
                  )}`,
                  ...properties.map(p =>
                    this.propertyAssignmentForField(p, properties)
                  )
                ],
                ", "
              ) || swift`:`,
              swift`])`
            )
          );
        });
      }
    }
  }

  propertyAssignmentForField(
    field: {
      responseKey: string;
      propertyName: string;
      type: GraphQLType;
      isConditional?: boolean;
      structName?: string;
    },
    properties: { propertyName: string }[]
  ): SwiftSource {
    const {
      responseKey,
      propertyName,
      type,
      isConditional,
      structName
    } = field;
    const parameterName = this.helpers.internalParameterName(
      propertyName,
      properties
    );
    const valueExpression = isCompositeType(getNamedType(type))
      ? this.helpers.mapExpressionForType(
          type,
          isConditional,
          expression => swift`${expression}.resultMap`,
          SwiftSource.identifier(parameterName),
          structName!,
          "ResultMap"
        )
      : SwiftSource.identifier(parameterName);
    return swift`${SwiftSource.string(responseKey)}: ${valueExpression}`;
  }

  propertyDeclarationForField(field: Field & Property) {
    const {
      responseKey,
      propertyName,
      typeName,
      type,
      isOptional,
      isConditional
    } = field;

    const unmodifiedFieldType = getNamedType(type);

    this.printNewlineIfNeeded();

    this.comment(field.description);
    this.deprecationAttributes(field.isDeprecated, field.deprecationReason);

    this.printOnNewline(swift`public var ${propertyName}: ${typeName}`);
    this.withinBlock(() => {
      if (isCompositeType(unmodifiedFieldType)) {
        const structName = this.helpers.structNameForPropertyName(propertyName);

        if (isList(type)) {
          this.printOnNewline(swift`get`);
          this.withinBlock(() => {
            const resultMapTypeName = this.helpers.typeNameFromGraphQLType(
              type,
              "ResultMap",
              false
            );
            let expression;
            if (isOptional) {
              expression = swift`(resultMap[${SwiftSource.string(
                responseKey
              )}] as? ${resultMapTypeName})`;
            } else {
              expression = swift`(resultMap[${SwiftSource.string(
                responseKey
              )}] as! ${resultMapTypeName})`;
            }
            this.printOnNewline(
              swift`return ${this.helpers.mapExpressionForType(
                type,
                isConditional,
                expression =>
                  swift`${structName}(unsafeResultMap: ${expression})`,
                expression,
                "ResultMap",
                structName
              )}`
            );
          });
          this.printOnNewline(swift`set`);
          this.withinBlock(() => {
            let newValueExpression = this.helpers.mapExpressionForType(
              type,
              isConditional,
              expression => swift`${expression}.resultMap`,
              swift`newValue`,
              structName,
              "ResultMap"
            );
            this.printOnNewline(
              swift`resultMap.updateValue(${newValueExpression}, forKey: ${SwiftSource.string(
                responseKey
              )})`
            );
          });
        } else {
          this.printOnNewline(swift`get`);
          this.withinBlock(() => {
            if (isOptional) {
              this.printOnNewline(
                swift`return (resultMap[${SwiftSource.string(
                  responseKey
                )}] as? ResultMap).flatMap { ${structName}(unsafeResultMap: $0) }`
              );
            } else {
              this.printOnNewline(
                swift`return ${structName}(unsafeResultMap: resultMap[${SwiftSource.string(
                  responseKey
                )}]! as! ResultMap)`
              );
            }
          });
          this.printOnNewline(swift`set`);
          this.withinBlock(() => {
            let newValueExpression;
            if (isOptional) {
              newValueExpression = "newValue?.resultMap";
            } else {
              newValueExpression = "newValue.resultMap";
            }
            this.printOnNewline(
              swift`resultMap.updateValue(${newValueExpression}, forKey: ${SwiftSource.string(
                responseKey
              )})`
            );
          });
        }
      } else {
        this.printOnNewline(swift`get`);
        this.withinBlock(() => {
          if (isOptional) {
            this.printOnNewline(
              swift`return resultMap[${SwiftSource.string(
                responseKey
              )}] as? ${typeName.slice(0, -1)}`
            );
          } else {
            this.printOnNewline(
              swift`return resultMap[${SwiftSource.string(
                responseKey
              )}]! as! ${typeName}`
            );
          }
        });
        this.printOnNewline(swift`set`);
        this.withinBlock(() => {
          this.printOnNewline(
            swift`resultMap.updateValue(newValue, forKey: ${SwiftSource.string(
              responseKey
            )})`
          );
        });
      }
    });
  }

  propertyDeclarationForVariant(variant: Property & Struct) {
    const { propertyName, typeName, structName } = variant;

    this.printNewlineIfNeeded();
    this.printOnNewline(swift`public var ${propertyName}: ${typeName}`);
    this.withinBlock(() => {
      this.printOnNewline(swift`get`);
      this.withinBlock(() => {
        this.printOnNewline(
          swift`if !${structName}.possibleTypes.contains(__typename) { return nil }`
        );
        this.printOnNewline(
          swift`return ${structName}(unsafeResultMap: resultMap)`
        );
      });
      this.printOnNewline(swift`set`);
      this.withinBlock(() => {
        this.printOnNewline(
          swift`guard let newValue = newValue else { return }`
        );
        this.printOnNewline(swift`resultMap = newValue.resultMap`);
      });
    });
  }

  initializerDeclarationForProperties(properties: Property[]) {
    this.printOnNewline(swift`public init`);
    this.parametersForProperties(properties);

    this.withinBlock(() => {
      properties.forEach(({ propertyName }) => {
        this.printOnNewline(
          swift`self.${propertyName} = ${this.helpers.internalParameterName(
            propertyName,
            properties
          )}`
        );
      });
    });
  }

  parametersForProperties(properties: Property[]) {
    this.print(swift`(`);
    this.print(
      join(
        properties.map(({ propertyName, typeName, isOptional }) => {
          const internalName = this.helpers.internalParameterName(
            propertyName,
            properties
          );
          const decl =
            internalName === propertyName
              ? propertyName
              : swift`${propertyName} ${internalName}`;
          return join([
            swift`${decl}: ${typeName}`,
            isOptional ? swift` = nil` : undefined
          ]);
        }),
        ", "
      )
    );
    this.print(swift`)`);
  }

  typeCaseInitialization(typeCase: TypeCase) {
    if (typeCase.variants.length < 1) {
      this.selectionSetInitialization(typeCase.default);
      return;
    }

    this.print(swift`[`);
    this.withIndent(() => {
      this.printOnNewline(swift`GraphQLTypeCase(`);
      this.withIndent(() => {
        this.printOnNewline(swift`variants: [`);
        this.print(
          join(
            typeCase.variants.flatMap(variant => {
              const structName = this.helpers.structNameForVariant(variant);
              return variant.possibleTypes.map(
                type =>
                  swift`${SwiftSource.string(
                    type.toString()
                  )}: ${structName}.selections`
              );
            }),
            ", "
          )
        );
        this.print(swift`],`);
        this.printOnNewline(swift`default: `);
        this.selectionSetInitialization(typeCase.default);
      });
      this.printOnNewline(swift`)`);
    });
    this.printOnNewline(swift`]`);
  }

  selectionSetInitialization(selectionSet: SelectionSet) {
    this.print(swift`[`);
    this.withIndent(() => {
      for (const selection of selectionSet.selections) {
        switch (selection.kind) {
          case "Field": {
            const { name, alias, args, type } = selection;
            const responseKey = selection.alias || selection.name;
            const structName = this.helpers.structNameForPropertyName(
              responseKey
            );

            this.printOnNewline(swift`GraphQLField(`);
            this.print(
              join(
                [
                  swift`${SwiftSource.string(name)}`,
                  alias
                    ? swift`alias: ${SwiftSource.string(alias)}`
                    : undefined,
                  args && args.length
                    ? swift`arguments: ${this.helpers.dictionaryLiteralForFieldArguments(
                        args
                      )}`
                    : undefined,
                  swift`type: ${this.helpers.fieldTypeEnum(type, structName)}`
                ],
                ", "
              )
            );
            this.print(swift`),`);
            break;
          }
          case "BooleanCondition":
            this.printOnNewline(swift`GraphQLBooleanCondition(`);
            this.print(
              join(
                [
                  swift`variableName: ${SwiftSource.string(
                    selection.variableName
                  )}`,
                  swift`inverted: ${selection.inverted}`,
                  swift`selections: `
                ],
                ", "
              )
            );
            this.selectionSetInitialization(selection.selectionSet);
            this.print(swift`),`);
            break;
          case "TypeCondition": {
            this.printOnNewline(swift`GraphQLTypeCondition(`);
            this.print(
              join(
                [
                  swift`possibleTypes: [${join(
                    selection.selectionSet.possibleTypes.map(
                      type => swift`${SwiftSource.string(type.name)}`
                    ),
                    ", "
                  )}]`,
                  swift`selections: `
                ],
                ", "
              )
            );
            this.selectionSetInitialization(selection.selectionSet);
            this.print(swift`),`);
            break;
          }
          case "FragmentSpread": {
            const structName = this.helpers.structNameForFragmentName(
              selection.fragmentName
            );
            this.printOnNewline(
              swift`GraphQLFragmentSpread(${structName}.self),`
            );
            break;
          }
        }
      }
    });
    this.printOnNewline(swift`]`);
  }

  /**
   * Generates a type declaration for the given `GraphQLType`
   *
   * @param type The graphQLType to generate a type declaration for.
   * @param outputIndividualFiles If this operation is being output as individual files, to help prevent
   *                              redundant usages of the `public` modifier in enum extensions.
   */
  typeDeclarationForGraphQLType(
    type: GraphQLType,
    outputIndividualFiles: boolean
  ) {
    if (isEnumType(type)) {
      this.enumerationDeclaration(type);
    } else if (isInputObjectType(type)) {
      this.structDeclarationForInputObjectType(type, outputIndividualFiles);
    }
  }

  enumerationDeclaration(type: GraphQLEnumType) {
    const { name, description } = type;
    const values = type.getValues().filter(value => {
      return (
        !value.isDeprecated || !this.context.options.omitDeprecatedEnumCases
      );
    });

    this.printNewlineIfNeeded();
    this.comment(description || undefined);
    this.printOnNewline(
      swift`public enum ${name}: RawRepresentable, Equatable, Hashable, CaseIterable, Apollo.JSONDecodable, Apollo.JSONEncodable`
    );
    this.withinBlock(() => {
      this.printOnNewline(swift`public typealias RawValue = String`);

      values.forEach(value => {
        this.comment(value.description || undefined);
        this.deprecationAttributes(
          value.isDeprecated,
          value.deprecationReason || undefined
        );
        this.printOnNewline(
          swift`case ${this.helpers.enumCaseName(value.name)}`
        );
      });
      this.comment("Auto generated constant for unknown enum values");
      this.printOnNewline(swift`case __unknown(RawValue)`);

      this.printNewlineIfNeeded();
      this.printOnNewline(swift`public init?(rawValue: RawValue)`);
      this.withinBlock(() => {
        this.printOnNewline(swift`switch rawValue`);
        this.withinBlock(() => {
          values.forEach(value => {
            this.printOnNewline(
              swift`case ${SwiftSource.string(
                value.value
              )}: self = ${this.helpers.enumDotCaseName(value.name)}`
            );
          });
          this.printOnNewline(swift`default: self = .__unknown(rawValue)`);
        });
      });

      this.printNewlineIfNeeded();
      this.printOnNewline(swift`public var rawValue: RawValue`);
      this.withinBlock(() => {
        this.printOnNewline(swift`switch self`);
        this.withinBlock(() => {
          values.forEach(value => {
            this.printOnNewline(
              swift`case ${this.helpers.enumDotCaseName(
                value.name
              )}: return ${SwiftSource.string(value.value)}`
            );
          });
          this.printOnNewline(swift`case .__unknown(let value): return value`);
        });
      });

      this.printNewlineIfNeeded();
      this.printOnNewline(
        swift`public static func == (lhs: ${name}, rhs: ${name}) -> Bool`
      );
      this.withinBlock(() => {
        this.printOnNewline(swift`switch (lhs, rhs)`);
        this.withinBlock(() => {
          values.forEach(value => {
            const enumDotCaseName = this.helpers.enumDotCaseName(value.name);
            const tuple = swift`(${enumDotCaseName}, ${enumDotCaseName})`;
            this.printOnNewline(swift`case ${tuple}: return true`);
          });
          this.printOnNewline(
            swift`case (.__unknown(let lhsValue), .__unknown(let rhsValue)): return lhsValue == rhsValue`
          );
          this.printOnNewline(swift`default: return false`);
        });
      });

      this.printNewlineIfNeeded();
      this.printOnNewline(swift`public static var allCases: [${name}]`);
      this.withinBlock(() => {
        this.printOnNewline(swift`return [`);
        values.forEach(value => {
          const enumDotCaseName = this.helpers.enumDotCaseName(value.name);
          this.withIndent(() => {
            this.printOnNewline(swift`${enumDotCaseName},`);
          });
        });
        this.printOnNewline(swift`]`);
      });
    });
  }

  /**
   * Generates a struct for a `GraphQLInputObjectType`.
   *
   * @param type The input type to generate code for
   * @param outputIndividualFiles If this operation is being output as individual files, to help prevent
   *                              redundant usages of the `public` modifier in enum extensions.
   */
  structDeclarationForInputObjectType(
    type: GraphQLInputObjectType,
    outputIndividualFiles: boolean
  ) {
    const { name: structName, description } = type;
    const adoptedProtocols = ["GraphQLMapConvertible"];
    const fields = Object.values(type.getFields());

    const properties = fields.map(
      this.helpers.propertyFromInputField,
      this.helpers
    );

    properties.forEach(property => {
      if (property.isOptional) {
        property.typeName = `Swift.Optional<${property.typeName}>`;
      }
    });

    this.structDeclaration(
      { structName, description: description || undefined, adoptedProtocols },
      outputIndividualFiles,
      () => {
        this.printOnNewline(swift`public var graphQLMap: GraphQLMap`);

        this.printNewlineIfNeeded();

        if (properties.length > 0) {
          this.comment("- Parameters:");
          properties.forEach(property => {
            var propertyDescription = "";
            if (property.description) {
              propertyDescription = `: ${property.description}`;
            }
            this.comment(
              `  - ${property.propertyName}${propertyDescription}`,
              false
            );
          });
        }

        this.printOnNewline(swift`public init`);
        this.print(swift`(`);
        this.print(
          join(
            properties.map(({ propertyName, typeName, isOptional }) => {
              const internalName = this.helpers.internalParameterName(
                propertyName,
                properties
              );
              const decl =
                internalName === propertyName
                  ? propertyName
                  : swift`${propertyName} ${internalName}`;
              return join([
                swift`${decl}: ${typeName}`,
                isOptional ? swift` = nil` : undefined
              ]);
            }),
            ", "
          )
        );
        this.print(swift`)`);

        this.withinBlock(() => {
          this.printOnNewline(
            wrap(
              swift`graphQLMap = [`,
              join(
                properties.map(
                  ({ name, propertyName }) =>
                    swift`${SwiftSource.string(
                      name
                    )}: ${this.helpers.internalParameterName(
                      propertyName,
                      properties
                    )}`
                ),
                ", "
              ) || swift`:`,
              swift`]`
            )
          );
        });

        for (const {
          name,
          propertyName,
          typeName,
          description,
          isOptional
        } of properties) {
          this.printNewlineIfNeeded();
          this.comment(description || undefined);
          this.printOnNewline(swift`public var ${propertyName}: ${typeName}`);
          this.withinBlock(() => {
            this.printOnNewline(swift`get`);
            this.withinBlock(() => {
              if (isOptional) {
                this.printOnNewline(
                  swift`return graphQLMap[${SwiftSource.string(
                    name
                  )}] as? ${typeName} ?? ${typeName}.none`
                );
              } else {
                this.printOnNewline(
                  swift`return graphQLMap[${SwiftSource.string(
                    name
                  )}] as! ${typeName}`
                );
              }
            });
            this.printOnNewline(swift`set`);
            this.withinBlock(() => {
              this.printOnNewline(
                swift`graphQLMap.updateValue(newValue, forKey: ${SwiftSource.string(
                  name
                )})`
              );
            });
          });
        }
      }
    );
  }
}