All Downloads are FREE. Search and download functionalities are using the official Maven repository.

node_modules.apollo-codegen.src.javascript.flow.codeGeneration.ts Maven / Gradle / Ivy

There is a newer version: 3.3.1
Show newest version
import * as t from '@babel/types';
import { stripIndent } from 'common-tags';
import {
  GraphQLEnumType,
  GraphQLInputObjectType,
} from 'graphql';
import * as path from 'path';

import {
  CompilerContext,
  Operation,
  Fragment,
  SelectionSet,
  Field,
} from '../../compiler';

import {
  typeCaseForSelectionSet,
  Variant
} from '../../compiler/visitors/typeCase';

import {
  collectAndMergeFields
} from '../../compiler/visitors/collectAndMergeFields';

import { BasicGeneratedFile } from '../../utilities/CodeGenerator';
import FlowGenerator, { ObjectProperty, FlowCompilerOptions, } from './language';
import Printer from './printer';

class FlowGeneratedFile implements BasicGeneratedFile {
  fileContents: string;

  constructor(fileContents: string) {
    this.fileContents = fileContents;
  }
  get output() {
    return this.fileContents
  }
}

function printEnumsAndInputObjects(generator: FlowAPIGenerator, context: CompilerContext) {
  generator.printer.enqueue(stripIndent`
    //==============================================================
    // START Enums and Input Objects
    // All enums and input objects are included in every output file
    // for now, but this will be changed soon.
    // TODO: Link to issue to fix this.
    //==============================================================
  `);

  context.typesUsed
    .filter(type => (type instanceof GraphQLEnumType))
    .forEach((enumType) => {
      generator.typeAliasForEnumType(enumType as GraphQLEnumType);
    });

  context.typesUsed
    .filter(type => type instanceof GraphQLInputObjectType)
    .forEach((inputObjectType) => {
      generator.typeAliasForInputObjectType(inputObjectType as GraphQLInputObjectType);
    });

  generator.printer.enqueue(stripIndent`
    //==============================================================
    // END Enums and Input Objects
    //==============================================================
  `)
}

export function generateSource(
  context: CompilerContext,
) {
  const generator = new FlowAPIGenerator(context);
  const generatedFiles: { [filePath: string]: FlowGeneratedFile } = {};

  Object.values(context.operations)
    .forEach((operation) => {
      generator.fileHeader();
      generator.typeAliasesForOperation(operation);
      printEnumsAndInputObjects(generator, context);

      const output = generator.printer.printAndClear();

      const outputFilePath = path.join(
        path.dirname(operation.filePath),
        '__generated__',
        `${operation.operationName}.js`
      );

      generatedFiles[outputFilePath] = new FlowGeneratedFile(output);
    });

  Object.values(context.fragments)
    .forEach((fragment) => {
      generator.fileHeader();
      generator.typeAliasesForFragment(fragment);
      printEnumsAndInputObjects(generator, context);

      const output = generator.printer.printAndClear();

      const outputFilePath = path.join(
        path.dirname(fragment.filePath),
        '__generated__',
        `${fragment.fragmentName}.js`
      );

      generatedFiles[outputFilePath] = new FlowGeneratedFile(output);
    });

  return generatedFiles;
}

export class FlowAPIGenerator extends FlowGenerator {
  context: CompilerContext
  printer: Printer
  scopeStack: string[]

  constructor(context: CompilerContext) {
    super(context.options as FlowCompilerOptions);

    this.context = context;
    this.printer = new Printer();
    this.scopeStack = [];
  }

  fileHeader() {
    this.printer.enqueue(
      stripIndent`
        /* @flow */
        /* eslint-disable */
        // This file was automatically generated and should not be edited.
      `
    );
  }

  public typeAliasForEnumType(enumType: GraphQLEnumType) {
    this.printer.enqueue(this.enumerationDeclaration(enumType));
  }

  public typeAliasForInputObjectType(inputObjectType: GraphQLInputObjectType) {
    const typeAlias = this.inputObjectDeclaration(inputObjectType);

    const { description } = inputObjectType;
    const exportDeclarationOptions = description
      ? { comments: ` ${description.replace('\n', ' ')}` }
      : {};

    const exportedTypeAlias = this.exportDeclaration(typeAlias, exportDeclarationOptions);
    this.printer.enqueue(exportedTypeAlias);

  }

  public typeAliasesForOperation(operation: Operation) {
    const {
      operationType,
      operationName,
      variables,
      selectionSet
    } = operation;

    this.scopeStackPush(operationName);

    this.printer.enqueue(stripIndent`
      // ====================================================
      // GraphQL ${operationType} operation: ${operationName}
      // ====================================================
    `)

    // The root operation only has one variant
    // Do we need to get exhaustive variants anyway?
    const variants = this.getVariantsForSelectionSet(selectionSet);

    const variant = variants[0];
    const properties = this.getPropertiesForVariant(variant);

    const exportedTypeAlias = this.exportDeclaration(
      this.typeAliasObject(operationName, properties)
    );

    this.printer.enqueue(exportedTypeAlias);
    this.scopeStackPop();

    // Generate the variables interface if the operation has any variables
    if (variables.length > 0) {
      const interfaceName = operationName + 'Variables';
      this.scopeStackPush(interfaceName);
      this.printer.enqueue(this.exportDeclaration(
        this.typeAliasObject(interfaceName, variables.map((variable) => ({
          name: variable.name,
          annotation: this.typeAnnotationFromGraphQLType(variable.type)
        })), { keyInheritsNullability: true })
      ));
      this.scopeStackPop();
    }
  }

  public typeAliasesForFragment(fragment: Fragment) {
    const {
      fragmentName,
      selectionSet
    } = fragment;

    this.scopeStackPush(fragmentName);

    this.printer.enqueue(stripIndent`
      // ====================================================
      // GraphQL fragment: ${fragmentName}
      // ====================================================
    `);

    const variants = this.getVariantsForSelectionSet(selectionSet);

    if (variants.length === 1) {
      const properties = this.getPropertiesForVariant(variants[0]);

      const name = this.annotationFromScopeStack(this.scopeStack).id.name;
      const exportedTypeAlias = this.exportDeclaration(
        this.typeAliasObject(
          name,
          properties
        )
      );

      this.printer.enqueue(exportedTypeAlias);
    } else {
      const unionMembers: t.FlowTypeAnnotation[] = [];
      variants.forEach(variant => {
        this.scopeStackPush(variant.possibleTypes[0].toString());
        const properties = this.getPropertiesForVariant(variant);

        const name = this.annotationFromScopeStack(this.scopeStack).id.name;
        const exportedTypeAlias = this.exportDeclaration(
          this.typeAliasObject(
            name,
            properties
          )
        );

        this.printer.enqueue(exportedTypeAlias);

        unionMembers.push(this.annotationFromScopeStack(this.scopeStack));

        this.scopeStackPop();
      });

      this.printer.enqueue(
        this.exportDeclaration(
          this.typeAliasGenericUnion(
            this.annotationFromScopeStack(this.scopeStack).id.name,
            unionMembers
          )
        )
      );
    }

    this.scopeStackPop();
  }

  private getVariantsForSelectionSet(selectionSet: SelectionSet) {
    return this.getTypeCasesForSelectionSet(selectionSet).exhaustiveVariants;
  }

  private getTypeCasesForSelectionSet(selectionSet: SelectionSet) {
    return typeCaseForSelectionSet(
      selectionSet,
      this.context.options.mergeInFieldsFromFragmentSpreads
    );
  }

  private getPropertiesForVariant(variant: Variant): ObjectProperty[] {
    const fields = collectAndMergeFields(
      variant,
      this.context.options.mergeInFieldsFromFragmentSpreads
    );

    return fields.map(field => {
      const fieldName = field.alias !== undefined ? field.alias : field.name;
      this.scopeStackPush(fieldName);

      let res;
      if (field.selectionSet) {
        const generatedTypeName = this.annotationFromScopeStack(this.scopeStack);
        res = this.handleFieldSelectionSetValue(
          generatedTypeName,
          field
        );
      } else {
        res = this.handleFieldValue(
          field,
          variant
        );
      }

      this.scopeStackPop();
      return res;
    });
  }

  private handleFieldSelectionSetValue(
    generatedTypeName: t.GenericTypeAnnotation,
    field: Field,
  ) {
    const { selectionSet } = field;

    const annotation = this.typeAnnotationFromGraphQLType(field.type, generatedTypeName.id.name);

    const typeCase = this.getTypeCasesForSelectionSet(selectionSet as SelectionSet);
    const variants = typeCase.exhaustiveVariants;

    let exportedTypeAlias;
    if (variants.length === 1) {
      const variant = variants[0];
      const properties = this.getPropertiesForVariant(variant);
      exportedTypeAlias = this.exportDeclaration(
        this.typeAliasObject(
          this.annotationFromScopeStack(this.scopeStack).id.name,
          properties
        )
      );
    } else {
      const propertySets = variants.map(variant => {
        this.scopeStackPush(variant.possibleTypes[0].toString())
        const properties = this.getPropertiesForVariant(variant);
        this.scopeStackPop();
        return properties;
      })

      exportedTypeAlias = this.exportDeclaration(
        this.typeAliasObjectUnion(
          generatedTypeName.id.name,
          propertySets
        )
      );
    }

    this.printer.enqueue(exportedTypeAlias);

    return {
      name: field.alias ? field.alias : field.name,
      description: field.description,
      annotation: annotation,
    };
  }

  private handleFieldValue(field: Field, variant: Variant) {
    let res;
    if (field.name === '__typename') {
      const annotations = variant.possibleTypes
        .map(type => {
          const annotation = t.stringLiteralTypeAnnotation();
          annotation.value = type.toString();
          return annotation;
        });

      res = {
        name: field.alias ? field.alias : field.name,
        description: field.description,
        annotation: t.unionTypeAnnotation(annotations)
      };
    } else {
      // TODO: Double check that this works
      res = {
        name: field.alias ? field.alias : field.name,
        description: field.description,
        annotation: this.typeAnnotationFromGraphQLType(field.type)
      };
    }

    return res;
  }

  public get output(): string {
    return this.printer.print();
  }

  scopeStackPush(name: string) {
    this.scopeStack.push(name);
  }

  scopeStackPop() {
    const popped = this.scopeStack.pop()
    return popped;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy