graphql-language-service-server
Version:
Server process backing the GraphQL Language Service
277 lines • 12.8 kB
JavaScript
import { GraphQLError, Kind, parse, print, isTypeDefinitionNode, typeFromAST, } from 'graphql';
import { getAutocompleteSuggestions, getHoverInformation, validateQuery, getRange, DIAGNOSTIC_SEVERITY, getOutline, getDefinitionQueryResultForFragmentSpread, getDefinitionQueryResultForDefinitionNode, getDefinitionQueryResultForNamedType, getDefinitionQueryResultForField, getASTNodeAtPosition, getTokenAtPosition, getTypeInfo, getDefinitionQueryResultForArgument, } from 'graphql-language-service';
import { SymbolKind, } from 'vscode-languageserver-types';
const KIND_TO_SYMBOL_KIND = {
[Kind.FIELD]: SymbolKind.Field,
[Kind.OPERATION_DEFINITION]: SymbolKind.Class,
[Kind.FRAGMENT_DEFINITION]: SymbolKind.Class,
[Kind.FRAGMENT_SPREAD]: SymbolKind.Struct,
[Kind.OBJECT_TYPE_DEFINITION]: SymbolKind.Class,
[Kind.ENUM_TYPE_DEFINITION]: SymbolKind.Enum,
[Kind.ENUM_VALUE_DEFINITION]: SymbolKind.EnumMember,
[Kind.INPUT_OBJECT_TYPE_DEFINITION]: SymbolKind.Class,
[Kind.INPUT_VALUE_DEFINITION]: SymbolKind.Field,
[Kind.FIELD_DEFINITION]: SymbolKind.Field,
[Kind.INTERFACE_TYPE_DEFINITION]: SymbolKind.Interface,
[Kind.DOCUMENT]: SymbolKind.File,
FieldWithArguments: SymbolKind.Method,
};
function getKind(tree) {
if (tree.kind === 'FieldDefinition' &&
tree.children &&
tree.children.length > 0) {
return KIND_TO_SYMBOL_KIND.FieldWithArguments;
}
return KIND_TO_SYMBOL_KIND[tree.kind];
}
export class GraphQLLanguageService {
constructor(cache, logger) {
this._graphQLCache = cache;
this._graphQLConfig = cache.getGraphQLConfig();
this._logger = logger;
}
getConfigForURI(uri) {
const config = this._graphQLCache.getProjectForFile(uri);
if (config) {
return config;
}
}
async getDiagnostics(document, uri, isRelayCompatMode) {
var _a, _b;
let documentHasExtensions = false;
const projectConfig = this.getConfigForURI(uri);
if (!projectConfig || !document || document.trim().length < 2) {
return [];
}
const { schema: schemaPath, name: projectName, extensions } = projectConfig;
try {
const documentAST = parse(document);
if (!schemaPath || uri !== schemaPath) {
documentHasExtensions = documentAST.definitions.some(definition => {
switch (definition.kind) {
case Kind.OBJECT_TYPE_DEFINITION:
case Kind.INTERFACE_TYPE_DEFINITION:
case Kind.ENUM_TYPE_DEFINITION:
case Kind.UNION_TYPE_DEFINITION:
case Kind.SCALAR_TYPE_DEFINITION:
case Kind.INPUT_OBJECT_TYPE_DEFINITION:
case Kind.SCALAR_TYPE_EXTENSION:
case Kind.OBJECT_TYPE_EXTENSION:
case Kind.INTERFACE_TYPE_EXTENSION:
case Kind.UNION_TYPE_EXTENSION:
case Kind.ENUM_TYPE_EXTENSION:
case Kind.INPUT_OBJECT_TYPE_EXTENSION:
case Kind.DIRECTIVE_DEFINITION:
return true;
}
return false;
});
}
}
catch (error) {
if (error instanceof GraphQLError) {
const range = getRange((_b = (_a = error.locations) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : { column: 0, line: 0 }, document);
return [
{
severity: DIAGNOSTIC_SEVERITY.Error,
message: error.message,
source: 'GraphQL: Syntax',
range,
},
];
}
throw error;
}
let source = document;
const fragmentDefinitions = await this._graphQLCache.getFragmentDefinitions(projectConfig);
const fragmentDependencies = await this._graphQLCache.getFragmentDependencies(document, fragmentDefinitions);
const dependenciesSource = fragmentDependencies.reduce((prev, cur) => `${prev} ${print(cur.definition)}`, '');
source = `${source} ${dependenciesSource}`;
let validationAst = null;
try {
validationAst = parse(source);
}
catch (_c) {
return [];
}
let customRules = null;
if ((extensions === null || extensions === void 0 ? void 0 : extensions.customValidationRules) &&
typeof extensions.customValidationRules === 'function') {
customRules = extensions.customValidationRules(this._graphQLConfig);
}
const schema = await this._graphQLCache.getSchema(projectName, documentHasExtensions);
if (!schema) {
return [];
}
return validateQuery(validationAst, schema, customRules, isRelayCompatMode);
}
async getAutocompleteSuggestions(query, position, filePath) {
var _a, _b, _c;
const projectConfig = this.getConfigForURI(filePath);
if (!projectConfig) {
return [];
}
const schema = await this._graphQLCache.getSchema(projectConfig.name);
if (!schema) {
return [];
}
let fragmentInfo = [];
try {
const fragmentDefinitions = await this._graphQLCache.getFragmentDefinitions(projectConfig);
fragmentInfo = Array.from(fragmentDefinitions).map(([, info]) => info.definition);
}
catch (_d) { }
return getAutocompleteSuggestions(schema, query, position, undefined, fragmentInfo, {
uri: filePath,
fillLeafsOnComplete: (_c = (_b = (_a = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.extensions) === null || _a === void 0 ? void 0 : _a.languageService) === null || _b === void 0 ? void 0 : _b.fillLeafsOnComplete) !== null && _c !== void 0 ? _c : false,
});
}
async getHoverInformation(query, position, filePath, options) {
const projectConfig = this.getConfigForURI(filePath);
if (!projectConfig) {
return '';
}
const schema = await this._graphQLCache.getSchema(projectConfig.name);
if (schema) {
return getHoverInformation(schema, query, position, undefined, options);
}
return '';
}
async getDefinition(query, position, filePath) {
const projectConfig = this.getConfigForURI(filePath);
if (!projectConfig) {
return null;
}
const schema = await this._graphQLCache.getSchema(projectConfig.name);
if (!schema) {
return null;
}
let ast;
try {
ast = parse(query);
}
catch (_a) {
return null;
}
const node = getASTNodeAtPosition(query, ast, position);
const type = node && typeFromAST(schema, node);
let queryResult = null;
if (node) {
switch (node.kind) {
case Kind.FRAGMENT_SPREAD:
queryResult = await this._getDefinitionForFragmentSpread(query, ast, node, filePath, projectConfig);
break;
case Kind.FRAGMENT_DEFINITION:
case Kind.OPERATION_DEFINITION:
queryResult = getDefinitionQueryResultForDefinitionNode(filePath, query, node);
break;
case Kind.NAMED_TYPE:
queryResult = await this._getDefinitionForNamedType(query, ast, node, filePath, projectConfig);
break;
case Kind.FIELD:
queryResult = await this._getDefinitionForField(query, ast, node, filePath, projectConfig, position);
break;
case Kind.ARGUMENT:
queryResult = await this._getDefinitionForArgument(query, ast, node, filePath, projectConfig, position);
break;
}
}
if (queryResult) {
return {
...queryResult,
node,
type,
};
}
return null;
}
async getDocumentSymbols(document, filePath) {
var _a;
const outline = await this.getOutline(document);
if (!outline) {
return [];
}
const output = [];
const input = outline.outlineTrees.map((tree) => [null, tree]);
while (input.length > 0) {
const res = input.pop();
if (!res) {
return [];
}
const [parent, tree] = res;
if (!tree) {
return [];
}
output.push({
name: (_a = tree.representativeName) !== null && _a !== void 0 ? _a : 'Anonymous',
kind: getKind(tree),
location: {
uri: filePath,
range: {
start: tree.startPosition,
end: tree.endPosition,
},
},
containerName: parent ? parent.representativeName : undefined,
});
input.push(...tree.children.map(child => [tree, child]));
}
return output;
}
async _getDefinitionForNamedType(query, ast, node, filePath, projectConfig) {
const objectTypeDefinitions = await this._graphQLCache.getObjectTypeDefinitions(projectConfig);
const dependencies = await this._graphQLCache.getObjectTypeDependenciesForAST(ast, objectTypeDefinitions);
const localOperationDefinitionInfos = ast.definitions
.filter(isTypeDefinitionNode)
.map((definition) => ({
filePath,
content: query,
definition,
}));
return getDefinitionQueryResultForNamedType(query, node, dependencies.concat(localOperationDefinitionInfos));
}
async _getDefinitionForField(query, _ast, _node, _filePath, projectConfig, position) {
var _a;
const token = getTokenAtPosition(query, position);
const schema = await this._graphQLCache.getSchema(projectConfig.name);
const typeInfo = getTypeInfo(schema, token.state);
const fieldName = (_a = typeInfo.fieldDef) === null || _a === void 0 ? void 0 : _a.name;
if (typeInfo && fieldName) {
const parentTypeName = typeInfo.parentType.toString();
const objectTypeDefinitions = await this._graphQLCache.getObjectTypeDefinitions(projectConfig);
const dependencies = [...objectTypeDefinitions.values()];
return getDefinitionQueryResultForField(fieldName, parentTypeName, dependencies);
}
return null;
}
async _getDefinitionForArgument(query, _ast, _node, _filePath, projectConfig, position) {
var _a, _b, _c, _d;
const token = getTokenAtPosition(query, position);
const schema = await this._graphQLCache.getSchema(projectConfig.name);
const typeInfo = getTypeInfo(schema, token.state);
const fieldName = (_a = typeInfo.fieldDef) === null || _a === void 0 ? void 0 : _a.name;
const argumentName = (_b = typeInfo.argDef) === null || _b === void 0 ? void 0 : _b.name;
if (typeInfo && fieldName && argumentName) {
const objectTypeDefinitions = await this._graphQLCache.getObjectTypeDefinitions(projectConfig);
const dependencies = [...objectTypeDefinitions.values()];
return getDefinitionQueryResultForArgument(argumentName, fieldName, (_d = (_c = typeInfo.argDef) === null || _c === void 0 ? void 0 : _c.type) === null || _d === void 0 ? void 0 : _d.name, dependencies);
}
return null;
}
async _getDefinitionForFragmentSpread(query, ast, node, filePath, projectConfig) {
const fragmentDefinitions = await this._graphQLCache.getFragmentDefinitions(projectConfig);
const dependencies = await this._graphQLCache.getFragmentDependenciesForAST(ast, fragmentDefinitions);
const localFragDefinitions = ast.definitions.filter(definition => definition.kind === Kind.FRAGMENT_DEFINITION);
const typeCastedDefs = localFragDefinitions;
const localFragInfos = typeCastedDefs.map((definition) => ({
filePath,
content: query,
definition,
}));
return getDefinitionQueryResultForFragmentSpread(query, node, dependencies.concat(localFragInfos));
}
async getOutline(documentText) {
return getOutline(documentText);
}
}
//# sourceMappingURL=GraphQLLanguageService.js.map