text: /**
   *  Copyright (c) 2019 GraphQL Contributors
   *  All rights reserved.
   *
   *  This source code is licensed under the license found in the
   *  LICENSE file in the root directory of this source tree.
   *
   *  @flow
   */
  
  /**
   * Ported from codemirror-graphql
   * https://github.com/graphql/codemirror-graphql/blob/master/src/info.js
   */
  
  import type { GraphQLSchema } from 'graphql';
  import type { ContextToken } from 'graphql-language-service-types';
  import type { Hover } from 'vscode-languageserver-types';
  import type { Position } from 'graphql-language-service-utils';
  import { getTokenAtPosition, getTypeInfo } from './getAutocompleteSuggestions';
  import { GraphQLNonNull, GraphQLList } from 'graphql';
  
  export function getHoverInformation(
    schema: GraphQLSchema,
    queryText: string,
    cursor: Position,
    contextToken?: ContextToken,
  ): Hover.contents {
    const token = contextToken || getTokenAtPosition(queryText, cursor);
  
    if (!schema || !token || !token.state) {
      return [];
    }
  
    const state = token.state;
    const kind = state.kind;
    const step = state.step;
    const typeInfo = getTypeInfo(schema, token.state);
    const options = { schema };
  
    // Given a Schema and a Token, produce the contents of an info tooltip.
    // To do this, create a div element that we will render "into" and then pass
    // it to various rendering functions.
    if (
      (kind === 'Field' && step === 0 && typeInfo.fieldDef) ||
      (kind === 'AliasedField' && step === 2 && typeInfo.fieldDef)
    ) {
      const into = [];
      renderField(into, typeInfo, options);
      renderDescription(into, options, typeInfo.fieldDef);
      return into.join('').trim();
    } else if (kind === 'Directive' && step === 1 && typeInfo.directiveDef) {
      const into = [];
      renderDirective(into, typeInfo, options);
      renderDescription(into, options, typeInfo.directiveDef);
      return into.join('').trim();
    } else if (kind === 'Argument' && step === 0 && typeInfo.argDef) {
      const into = [];
      renderArg(into, typeInfo, options);
      renderDescription(into, options, typeInfo.argDef);
      return into.join('').trim();
    } else if (
      kind === 'EnumValue' &&
      typeInfo.enumValue &&
      typeInfo.enumValue.description
    ) {
      const into = [];
      renderEnumValue(into, typeInfo, options);
      renderDescription(into, options, typeInfo.enumValue);
      return into.join('').trim();
    } else if (
      kind === 'NamedType' &&
      typeInfo.type &&
      typeInfo.type.description
    ) {
      const into = [];
      renderType(into, typeInfo, options, typeInfo.type);
      renderDescription(into, options, typeInfo.type);
      return into.join('').trim();
    }
  }
  
  function renderField(into, typeInfo, options) {
    renderQualifiedField(into, typeInfo, options);
    renderTypeAnnotation(into, typeInfo, options, typeInfo.type);
  }
  
  function renderQualifiedField(into, typeInfo, options) {
    if (!typeInfo.fieldDef) {
      return;
    }
    const fieldName = (typeInfo.fieldDef.name: string);
    if (fieldName.slice(0, 2) !== '__') {
      renderType(into, typeInfo, options, typeInfo.parentType);
      text(into, '.');
    }
    text(into, fieldName);
  }
  
  function renderDirective(into, typeInfo, options) {
    if (!typeInfo.directiveDef) {
      return;
    }
    const name = '@' + typeInfo.directiveDef.name;
    text(into, name);
  }
  
  function renderArg(into, typeInfo, options) {
    if (typeInfo.directiveDef) {
      renderDirective(into, typeInfo, options);
    } else if (typeInfo.fieldDef) {
      renderQualifiedField(into, typeInfo, options);
    }
  
    if (!typeInfo.argDef) {
      return;
    }
  
    const name = typeInfo.argDef.name;
    text(into, '(');
    text(into, name);
    renderTypeAnnotation(into, typeInfo, options, typeInfo.inputType);
    text(into, ')');
  }
  
  function renderTypeAnnotation(into, typeInfo, options, t) {
    text(into, ': ');
    renderType(into, typeInfo, options, t);
  }
  
  function renderEnumValue(into, typeInfo, options) {
    if (!typeInfo.enumValue) {
      return;
    }
    const name = typeInfo.enumValue.name;
    renderType(into, typeInfo, options, typeInfo.inputType);
    text(into, '.');
    text(into, name);
  }
  
  function renderType(into, typeInfo, options, t) {
    if (!t) {
      return;
    }
    if (t instanceof GraphQLNonNull) {
      renderType(into, typeInfo, options, t.ofType);
      text(into, '!');
    } else if (t instanceof GraphQLList) {
      text(into, '[');
      renderType(into, typeInfo, options, t.ofType);
      text(into, ']');
    } else {
      text(into, t.name);
    }
  }
  
  function renderDescription(into, options, def) {
    if (!def) {
      return;
    }
    const description =
      typeof def.description === 'string' ? def.description : null;
    if (description) {
      text(into, '\n\n');
      text(into, description);
    }
    renderDeprecation(into, options, def);
  }
  
  function renderDeprecation(into, options, def) {
    if (!def) {
      return;
    }
    const reason =
      typeof def.deprecationReason === 'string' ? def.deprecationReason : null;
    if (!reason) {
      return;
    }
    text(into, '\n\n');
    text(into, 'Deprecated: ');
    text(into, reason);
  }
  
  function text(into: string[], content: string) {
    into.push(content);
  }
  