UNPKG

graphql-language-service

Version:

The official, runtime independent Language Service for GraphQL

259 lines (237 loc) 6.5 kB
/** * Copyright (c) 2021 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. * */ /** * Ported from codemirror-graphql * https://github.com/graphql/blob/main/packages/codemirror-graphql/src/info.js */ import { GraphQLSchema, GraphQLNonNull, GraphQLList, GraphQLType, GraphQLField, GraphQLFieldConfig, } from 'graphql'; import type { ContextToken } from '../parser'; import { AllTypeInfo, IPosition } from '../types'; import { Hover } from 'vscode-languageserver-types'; import { getContextAtPosition } from '../parser'; export type HoverConfig = { useMarkdown?: boolean }; export function getHoverInformation( schema: GraphQLSchema, queryText: string, cursor: IPosition, contextToken?: ContextToken, config?: HoverConfig, ): Hover['contents'] { const options = { ...config, schema }; const context = getContextAtPosition(queryText, cursor, schema, contextToken); if (!context) { return ''; } const { typeInfo, token } = context; const { kind, step } = token.state; // 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) || (kind === 'ObjectField' && step === 0 && typeInfo.fieldDef) ) { const into: string[] = []; renderMdCodeStart(into, options); renderField(into, typeInfo, options); renderMdCodeEnd(into, options); renderDescription(into, options, typeInfo.fieldDef); return into.join('').trim(); } if (kind === 'Directive' && step === 1 && typeInfo.directiveDef) { const into: string[] = []; renderMdCodeStart(into, options); renderDirective(into, typeInfo, options); renderMdCodeEnd(into, options); renderDescription(into, options, typeInfo.directiveDef); return into.join('').trim(); } if (kind === 'Variable' && typeInfo.type) { const into: string[] = []; renderMdCodeStart(into, options); renderType(into, typeInfo, options, typeInfo.type); renderMdCodeEnd(into, options); renderDescription(into, options, typeInfo.type); return into.join('').trim(); } if (kind === 'Argument' && step === 0 && typeInfo.argDef) { const into: string[] = []; renderMdCodeStart(into, options); renderArg(into, typeInfo, options); renderMdCodeEnd(into, options); renderDescription(into, options, typeInfo.argDef); return into.join('').trim(); } if ( kind === 'EnumValue' && typeInfo.enumValue && 'description' in typeInfo.enumValue ) { const into: string[] = []; renderMdCodeStart(into, options); renderEnumValue(into, typeInfo, options); renderMdCodeEnd(into, options); renderDescription(into, options, typeInfo.enumValue); return into.join('').trim(); } if (kind === 'NamedType' && typeInfo.type && 'description' in typeInfo.type) { const into: string[] = []; renderMdCodeStart(into, options); renderType(into, typeInfo, options, typeInfo.type); renderMdCodeEnd(into, options); renderDescription(into, options, typeInfo.type); return into.join('').trim(); } return ''; } function renderMdCodeStart(into: string[], options: any) { if (options.useMarkdown) { text(into, '```graphql\n'); } } function renderMdCodeEnd(into: string[], options: any) { if (options.useMarkdown) { text(into, '\n```'); } } export function renderField( into: string[], typeInfo: AllTypeInfo, options: any, ) { renderQualifiedField(into, typeInfo, options); renderTypeAnnotation(into, typeInfo, options, typeInfo.type!); } function renderQualifiedField( into: string[], typeInfo: AllTypeInfo, options: any, ) { if (!typeInfo.fieldDef) { return; } const fieldName = typeInfo.fieldDef.name; if (fieldName.slice(0, 2) !== '__') { renderType(into, typeInfo, options, typeInfo.parentType!); text(into, '.'); } text(into, fieldName); } export function renderDirective( into: string[], typeInfo: AllTypeInfo, _options: any, ) { if (!typeInfo.directiveDef) { return; } const name = '@' + typeInfo.directiveDef.name; text(into, name); } export function renderArg(into: string[], typeInfo: AllTypeInfo, options: any) { if (typeInfo.directiveDef) { renderDirective(into, typeInfo, options); } else if (typeInfo.fieldDef) { renderQualifiedField(into, typeInfo, options); } if (!typeInfo.argDef) { return; } const { name } = typeInfo.argDef; text(into, '('); text(into, name); renderTypeAnnotation(into, typeInfo, options, typeInfo.inputType!); text(into, ')'); } function renderTypeAnnotation( into: string[], typeInfo: AllTypeInfo, options: any, t: GraphQLType, ) { text(into, ': '); renderType(into, typeInfo, options, t); } export function renderEnumValue( into: string[], typeInfo: AllTypeInfo, options: any, ) { if (!typeInfo.enumValue) { return; } const { name } = typeInfo.enumValue; renderType(into, typeInfo, options, typeInfo.inputType!); text(into, '.'); text(into, name); } export function renderType( into: string[], typeInfo: AllTypeInfo, options: any, t: GraphQLType, ) { 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: string[], options: any, // TODO: Figure out the right type for this one def: any, ) { 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: string[], _options: any, def: GraphQLField<any, any> | GraphQLFieldConfig<any, any>, ) { if (!def) { return; } const reason = 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); }