graphql-language-service-server
Version:
Server process backing the GraphQL Language Service
888 lines • 41.1 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.processDiagnosticsMessage = exports.MessageProcessor = void 0;
const node_fs_1 = require("node:fs");
const promises_1 = require("node:fs/promises");
const path = __importStar(require("node:path"));
const vscode_uri_1 = require("vscode-uri");
const graphql_language_service_1 = require("graphql-language-service");
const GraphQLLanguageService_1 = require("./GraphQLLanguageService");
const GraphQLCache_1 = require("./GraphQLCache");
const parseDocument_1 = require("./parseDocument");
const graphql_1 = require("graphql");
const node_os_1 = require("node:os");
const graphql_config_1 = require("graphql-config");
const constants_1 = require("./constants");
const fast_glob_1 = __importDefault(require("fast-glob"));
const common_1 = require("./common");
const debounce_promise_1 = __importDefault(require("debounce-promise"));
const configDocLink = 'https://www.npmjs.com/package/graphql-language-service-server#user-content-graphql-configuration-file';
function toPosition(position) {
return new graphql_language_service_1.Position(position.line, position.character);
}
class MessageProcessor {
constructor({ logger, fileExtensions, graphqlFileExtensions, loadConfigOptions, config, parser, tmpDir, connection, }) {
this._textDocumentCache = new Map();
this._isInitialized = false;
this._isGraphQLConfigMissing = null;
this._willShutdown = false;
this._rootPath = process.cwd();
if (config) {
this._providedConfig = config;
}
this._connection = connection;
this._logger = logger;
this._parser = async (text, uri) => {
const p = parser !== null && parser !== void 0 ? parser : parseDocument_1.parseDocument;
return p(text, uri, fileExtensions, graphqlFileExtensions, this._logger);
};
this._tmpDir = tmpDir || (0, node_os_1.tmpdir)();
this._tmpDirBase = path.join(this._tmpDir, 'graphql-language-service');
this._loadConfigOptions = { legacy: true, ...loadConfigOptions };
if (!(0, node_fs_1.existsSync)(this._tmpDirBase)) {
void (0, node_fs_1.mkdirSync)(this._tmpDirBase);
}
}
get connection() {
return this._connection;
}
set connection(connection) {
this._connection = connection;
}
async handleInitializeRequest(params, _token, configDir) {
if (!params) {
throw new Error('`params` argument is required to initialize.');
}
const serverCapabilities = {
capabilities: {
workspaceSymbolProvider: true,
documentSymbolProvider: true,
completionProvider: {
resolveProvider: true,
triggerCharacters: [' ', ':', '$', '(', '@', '\n'],
},
definitionProvider: true,
textDocumentSync: 1,
hoverProvider: true,
workspace: {
workspaceFolders: {
supported: true,
changeNotifications: true,
},
},
},
};
this._rootPath = configDir
? configDir.trim()
: params.rootUri || this._rootPath;
if (!this._rootPath) {
this._logger.warn('no rootPath configured in extension or server, defaulting to cwd');
}
this._logger.info(JSON.stringify({
type: 'usage',
messageType: 'initialize',
}));
return serverCapabilities;
}
async _initializeGraphQLCaches() {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
const settings = await this._connection.workspace.getConfiguration({
section: 'graphql-config',
});
const vscodeSettings = await this._connection.workspace.getConfiguration({
section: 'vscode-graphql',
});
this._settings = { ...settings, ...vscodeSettings };
const rootDir = ((_b = (_a = this._settings) === null || _a === void 0 ? void 0 : _a.load) === null || _b === void 0 ? void 0 : _b.rootDir.length)
? (_d = (_c = this._settings) === null || _c === void 0 ? void 0 : _c.load) === null || _d === void 0 ? void 0 : _d.rootDir
: this._rootPath;
if (settings === null || settings === void 0 ? void 0 : settings.dotEnvPath) {
require('dotenv').config({
path: path.resolve(rootDir, settings.dotEnvPath),
});
}
this._rootPath = rootDir;
this._loadConfigOptions = {
...Object.keys((_f = (_e = this._settings) === null || _e === void 0 ? void 0 : _e.load) !== null && _f !== void 0 ? _f : {}).reduce((agg, key) => {
var _a;
const value = (_a = this._settings) === null || _a === void 0 ? void 0 : _a.load[key];
if (value === undefined || value === null) {
delete agg[key];
}
return agg;
}, (_g = this._settings.load) !== null && _g !== void 0 ? _g : {}),
rootDir,
};
const onSchemaChange = (0, debounce_promise_1.default)(async (project) => {
const { cacheSchemaFileForLookup } = this.getCachedSchemaSettings(project);
if (!cacheSchemaFileForLookup) {
return;
}
const unwrappedSchema = (0, common_1.unwrapProjectSchema)(project);
const sdlOnly = (0, common_1.isProjectSDLOnly)(unwrappedSchema);
if (sdlOnly) {
return;
}
return this.cacheConfigSchemaFile(project);
}, 400);
try {
this._logger.level = ((_h = this._settings) === null || _h === void 0 ? void 0 : _h.debug) === true ? 1 : 0;
if (this._providedConfig) {
this._graphQLCache = new GraphQLCache_1.GraphQLCache({
config: this._providedConfig,
logger: this._logger,
parser: this._parser,
configDir: rootDir,
onSchemaChange,
schemaCacheTTL: (_j = this._settings) === null || _j === void 0 ? void 0 : _j.schemaCacheTTL,
});
this._languageService = new GraphQLLanguageService_1.GraphQLLanguageService(this._graphQLCache, this._logger);
}
else {
this._graphQLCache = await (0, GraphQLCache_1.getGraphQLCache)({
parser: this._parser,
loadConfigOptions: this._loadConfigOptions,
logger: this._logger,
onSchemaChange,
schemaCacheTTL: (_k = this._settings) === null || _k === void 0 ? void 0 : _k.schemaCacheTTL,
});
this._languageService = new GraphQLLanguageService_1.GraphQLLanguageService(this._graphQLCache, this._logger);
}
const config = this._graphQLCache.getGraphQLConfig();
if (config) {
await this._cacheAllProjectFiles(config);
this._isInitialized = true;
this._isGraphQLConfigMissing = false;
this._logger.info('GraphQL Language Server caches initialized');
}
}
catch (err) {
this._handleConfigError({ err });
}
}
_handleConfigError({ err }) {
var _a;
if (err instanceof graphql_config_1.ConfigNotFoundError || err instanceof graphql_config_1.ConfigEmptyError) {
this._isGraphQLConfigMissing = true;
this._logConfigError(err.message);
}
else if (err instanceof graphql_config_1.ProjectNotFoundError) {
this._logConfigError('Project not found for this file - make sure that a schema is present in the config file or for the project');
}
else if (err instanceof graphql_config_1.ConfigInvalidError) {
this._isGraphQLConfigMissing = true;
this._logConfigError(`Invalid configuration\n${err.message}`);
}
else if (err instanceof graphql_config_1.LoaderNoResultError) {
this._isGraphQLConfigMissing = true;
this._logConfigError(err.message);
return;
}
else {
this._isGraphQLConfigMissing = true;
this._logConfigError((_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err === null || err === void 0 ? void 0 : err.toString());
}
}
_logConfigError(errorMessage) {
this._logger.error('WARNING: graphql-config error, only highlighting is enabled:\n' +
errorMessage +
`\nfor more information on using 'graphql-config' with 'graphql-language-service-server', \nsee the documentation at ${configDocLink}`);
}
async _isGraphQLConfigFile(uri) {
var _a, _b, _c, _d, _e;
const configMatchers = ['graphql.config', 'graphqlrc', 'graphqlconfig'];
if ((_c = (_b = (_a = this._settings) === null || _a === void 0 ? void 0 : _a.load) === null || _b === void 0 ? void 0 : _b.fileName) === null || _c === void 0 ? void 0 : _c.length) {
configMatchers.push(this._settings.load.fileName);
}
const fileMatch = configMatchers
.filter(Boolean)
.some(v => { var _a; return (_a = uri.match(v)) === null || _a === void 0 ? void 0 : _a.length; });
if (fileMatch) {
return fileMatch;
}
if ((_d = uri.match('package.json')) === null || _d === void 0 ? void 0 : _d.length) {
try {
const pkgConfig = await (0, promises_1.readFile)(vscode_uri_1.URI.parse(uri).fsPath, 'utf-8');
return Boolean((_e = JSON.parse(pkgConfig)) === null || _e === void 0 ? void 0 : _e.graphql);
}
catch (_f) { }
}
return false;
}
async _loadConfigOrSkip(uri) {
try {
const isGraphQLConfigFile = await this._isGraphQLConfigFile(uri);
if (!this._isInitialized) {
if (this._isGraphQLConfigMissing === true && !isGraphQLConfigFile) {
return true;
}
await this._initializeGraphQLCaches();
return isGraphQLConfigFile;
}
if (isGraphQLConfigFile) {
await this._initializeGraphQLCaches();
}
return isGraphQLConfigFile;
}
catch (err) {
this._logger.error(String(err));
return true;
}
}
async handleDidOpenOrSaveNotification(params) {
var _a, _b;
const { textDocument } = params;
const { uri } = textDocument;
const shouldSkip = await this._loadConfigOrSkip(uri);
if (shouldSkip) {
return { uri, diagnostics: [] };
}
if (!textDocument) {
throw new Error('`textDocument` argument is required.');
}
const diagnostics = [];
if (!this._isInitialized) {
return { uri, diagnostics };
}
try {
const project = this._graphQLCache.getProjectForFile(uri);
if (project) {
const text = 'text' in textDocument && textDocument.text;
const { contents } = await this._parseAndCacheFile(uri, project, text);
if (((_b = (_a = project === null || project === void 0 ? void 0 : project.extensions) === null || _a === void 0 ? void 0 : _a.languageService) === null || _b === void 0 ? void 0 : _b.enableValidation) !== false) {
await Promise.all(contents.map(async ({ query, range }) => {
const results = await this._languageService.getDiagnostics(query, uri, this._isRelayCompatMode(query));
if (results && results.length > 0) {
diagnostics.push(...processDiagnosticsMessage(results, query, range));
}
}));
}
}
this._logger.log(JSON.stringify({
type: 'usage',
messageType: 'textDocument/didOpenOrSave',
projectName: project === null || project === void 0 ? void 0 : project.name,
fileName: uri,
}));
return { uri, diagnostics };
}
catch (err) {
this._handleConfigError({ err, uri });
return { uri, diagnostics };
}
}
async handleDidChangeNotification(params) {
var _a, _b, _c;
if (this._isGraphQLConfigMissing ||
!this._isInitialized ||
!this._graphQLCache) {
return null;
}
if (!((_a = params === null || params === void 0 ? void 0 : params.textDocument) === null || _a === void 0 ? void 0 : _a.uri) || !params.contentChanges) {
throw new Error('`textDocument.uri` and `contentChanges` arguments are required.');
}
const { textDocument, contentChanges } = params;
const { uri } = textDocument;
try {
const project = this._graphQLCache.getProjectForFile(uri);
if (!project) {
return { uri, diagnostics: [] };
}
const { contents } = await this._parseAndCacheFile(uri, project, contentChanges.at(-1).text);
const diagnostics = [];
if (((_c = (_b = project === null || project === void 0 ? void 0 : project.extensions) === null || _b === void 0 ? void 0 : _b.languageService) === null || _c === void 0 ? void 0 : _c.enableValidation) !== false) {
try {
await Promise.all(contents.map(async ({ query, range }) => {
const results = await this._languageService.getDiagnostics(query, uri, this._isRelayCompatMode(query));
if (results && results.length > 0) {
diagnostics.push(...processDiagnosticsMessage(results, query, range));
}
}));
}
catch (_d) { }
}
this._logger.log(JSON.stringify({
type: 'usage',
messageType: 'textDocument/didChange',
projectName: project === null || project === void 0 ? void 0 : project.name,
fileName: uri,
}));
return { uri, diagnostics };
}
catch (err) {
this._handleConfigError({ err, uri });
return { uri, diagnostics: [] };
}
}
async handleDidChangeConfiguration(_params) {
await this._initializeGraphQLCaches();
this._logger.log(JSON.stringify({
type: 'usage',
messageType: 'workspace/didChangeConfiguration',
}));
return {};
}
handleDidCloseNotification(params) {
if (!this._isInitialized) {
return;
}
if (!(params === null || params === void 0 ? void 0 : params.textDocument)) {
throw new Error('`textDocument` is required.');
}
const { textDocument } = params;
const { uri } = textDocument;
if (this._textDocumentCache.has(uri)) {
this._textDocumentCache.delete(uri);
}
const project = this._graphQLCache.getProjectForFile(uri);
this._logger.log(JSON.stringify({
type: 'usage',
messageType: 'textDocument/didClose',
projectName: project === null || project === void 0 ? void 0 : project.name,
fileName: uri,
}));
}
handleShutdownRequest() {
this._willShutdown = true;
}
handleExitNotification() {
process.exit(this._willShutdown ? 0 : 1);
}
validateDocumentAndPosition(params) {
var _a;
if (!((_a = params === null || params === void 0 ? void 0 : params.textDocument) === null || _a === void 0 ? void 0 : _a.uri) || !params.position) {
throw new Error('`textDocument.uri` and `position` arguments are required.');
}
}
async handleCompletionRequest(params) {
if (!this._isInitialized) {
return { items: [], isIncomplete: false };
}
this.validateDocumentAndPosition(params);
const { textDocument, position } = params;
const cachedDocument = this._getCachedDocument(textDocument.uri);
if (!cachedDocument) {
return { items: [], isIncomplete: false };
}
const found = cachedDocument.contents.find(content => {
const currentRange = content.range;
if (currentRange === null || currentRange === void 0 ? void 0 : currentRange.containsPosition(toPosition(position))) {
return true;
}
});
if (!found) {
return { items: [], isIncomplete: false };
}
const { query, range } = found;
if (range) {
position.line -= range.start.line;
}
const result = await this._languageService.getAutocompleteSuggestions(query, toPosition(position), textDocument.uri);
const project = this._graphQLCache.getProjectForFile(textDocument.uri);
this._logger.log(JSON.stringify({
type: 'usage',
messageType: 'textDocument/completion',
projectName: project === null || project === void 0 ? void 0 : project.name,
fileName: textDocument.uri,
}));
return { items: result, isIncomplete: false };
}
async handleHoverRequest(params) {
if (!this._isInitialized) {
return { contents: [] };
}
this.validateDocumentAndPosition(params);
const { textDocument, position } = params;
const cachedDocument = this._getCachedDocument(textDocument.uri);
if (!cachedDocument) {
return { contents: [] };
}
const found = cachedDocument.contents.find(content => {
const currentRange = content.range;
if (currentRange === null || currentRange === void 0 ? void 0 : currentRange.containsPosition(toPosition(position))) {
return true;
}
});
if (!found) {
return { contents: [] };
}
const { query, range } = found;
if (range) {
position.line -= range.start.line;
}
const result = await this._languageService.getHoverInformation(query, toPosition(position), textDocument.uri, { useMarkdown: true });
return {
contents: result,
};
}
async _parseAndCacheFile(uri, project, text) {
try {
const fileText = text || (await (0, promises_1.readFile)(vscode_uri_1.URI.parse(uri).fsPath, 'utf-8'));
const contents = await this._parser(fileText, uri);
const cachedDocument = this._textDocumentCache.get(uri);
const version = cachedDocument ? cachedDocument.version++ : 0;
await this._invalidateCache({ uri, version }, uri, contents);
await this._updateFragmentDefinition(uri, contents);
await this._updateObjectTypeDefinition(uri, contents, project);
await this._updateSchemaIfChanged(project, uri);
return { contents, version };
}
catch (_a) {
return { contents: [], version: 0 };
}
}
async handleWatchedFilesChangedNotification(params) {
const resultsForChanges = Promise.all(params.changes.map(async (change) => {
var _a, _b;
const shouldSkip = await this._loadConfigOrSkip(change.uri);
if (shouldSkip) {
return { uri: change.uri, diagnostics: [] };
}
if (change.type === graphql_language_service_1.FileChangeTypeKind.Created ||
change.type === graphql_language_service_1.FileChangeTypeKind.Changed) {
const { uri } = change;
try {
let diagnostics = [];
const project = this._graphQLCache.getProjectForFile(uri);
if (project) {
const { contents } = await this._parseAndCacheFile(uri, project);
if (((_b = (_a = project === null || project === void 0 ? void 0 : project.extensions) === null || _a === void 0 ? void 0 : _a.languageService) === null || _b === void 0 ? void 0 : _b.enableValidation) !== false) {
diagnostics = (await Promise.all(contents.map(async ({ query, range }) => {
const results = await this._languageService.getDiagnostics(query, uri, this._isRelayCompatMode(query));
if (results && results.length > 0) {
return processDiagnosticsMessage(results, query, range);
}
return [];
}))).reduce((left, right) => left.concat(right), diagnostics);
}
return { uri, diagnostics };
}
}
catch (_c) { }
return { uri, diagnostics: [] };
}
if (change.type === graphql_language_service_1.FileChangeTypeKind.Deleted) {
await this._updateFragmentDefinition(change.uri, []);
await this._updateObjectTypeDefinition(change.uri, []);
}
}));
this._logger.log(JSON.stringify({
type: 'usage',
messageType: 'workspace/didChangeWatchedFiles',
files: params.changes.map(change => change.uri),
}));
return resultsForChanges;
}
async handleDefinitionRequest(params, _token) {
var _a, _b;
if (!this._isInitialized) {
return [];
}
if (!(params === null || params === void 0 ? void 0 : params.textDocument) || !params.position) {
throw new Error('`textDocument` and `position` arguments are required.');
}
const { textDocument, position } = params;
const project = this._graphQLCache.getProjectForFile(textDocument.uri);
const cachedDocument = this._getCachedDocument(textDocument.uri);
if (!cachedDocument || !project) {
return [];
}
const found = cachedDocument.contents.find(content => {
const currentRange = content.range;
if (currentRange === null || currentRange === void 0 ? void 0 : currentRange.containsPosition(toPosition(position))) {
return true;
}
});
if (!found) {
return [];
}
const { query, range: parentRange } = found;
if (parentRange) {
position.line -= parentRange.start.line;
}
let result = null;
try {
result = await this._languageService.getDefinition(query, toPosition(position), textDocument.uri);
}
catch (_c) {
}
const inlineFragments = [];
try {
(0, graphql_1.visit)((0, graphql_1.parse)(query), {
FragmentDefinition(node) {
inlineFragments.push(node.name.value);
},
});
}
catch (_d) { }
const locateCommand = (_b = (_a = project === null || project === void 0 ? void 0 : project.extensions) === null || _a === void 0 ? void 0 : _a.languageService) === null || _b === void 0 ? void 0 : _b.locateCommand;
const formatted = result
? result.definitions.map(res => {
var _a, _b;
const defRange = res.range;
if (parentRange && res.name) {
const isInline = inlineFragments.includes(res.name);
const isEmbedded = constants_1.DEFAULT_SUPPORTED_EXTENSIONS.includes(path.extname(res.path));
if (isEmbedded || isInline) {
const cachedDoc = this._getCachedDocument(vscode_uri_1.URI.parse(res.path).toString());
const vOffset = isEmbedded
? (_b = (_a = cachedDoc === null || cachedDoc === void 0 ? void 0 : cachedDoc.contents[0].range) === null || _a === void 0 ? void 0 : _a.start.line) !== null && _b !== void 0 ? _b : 0
: parentRange.start.line;
defRange.setStart((defRange.start.line += vOffset), defRange.start.character);
defRange.setEnd((defRange.end.line += vOffset), defRange.end.character);
}
}
if (locateCommand && result && (result === null || result === void 0 ? void 0 : result.printedName)) {
const locateResult = this._getCustomLocateResult(project, result, locateCommand);
if (locateResult) {
return locateResult;
}
}
return {
uri: res.path,
range: defRange,
};
})
: [];
this._logger.log(JSON.stringify({
type: 'usage',
messageType: 'textDocument/definition',
projectName: project === null || project === void 0 ? void 0 : project.name,
fileName: textDocument.uri,
}));
return formatted;
}
_getCustomLocateResult(project, result, locateCommand) {
if (!result.printedName) {
return null;
}
try {
const locateResult = locateCommand(project.name, result.printedName, {
node: result.node,
type: result.type,
project,
});
if (typeof locateResult === 'string') {
const [uri, startLine = '1', endLine = '1'] = locateResult.split(':');
return {
uri,
range: new graphql_language_service_1.Range(new graphql_language_service_1.Position(parseInt(startLine, 10), 0), new graphql_language_service_1.Position(parseInt(endLine, 10), 0)),
};
}
return locateResult;
}
catch (error) {
this._logger.error('There was an error executing user defined locateCommand\n\n' +
error.toString());
return null;
}
}
async handleDocumentSymbolRequest(params) {
if (!this._isInitialized) {
return [];
}
if (!(params === null || params === void 0 ? void 0 : params.textDocument)) {
throw new Error('`textDocument` argument is required.');
}
const { textDocument } = params;
const cachedDocument = this._getCachedDocument(textDocument.uri);
if (!(cachedDocument === null || cachedDocument === void 0 ? void 0 : cachedDocument.contents[0])) {
return [];
}
if (this._settings.largeFileThreshold !== undefined &&
this._settings.largeFileThreshold <
cachedDocument.contents[0].query.length) {
return [];
}
this._logger.log(JSON.stringify({
type: 'usage',
messageType: 'textDocument/documentSymbol',
fileName: textDocument.uri,
}));
return this._languageService.getDocumentSymbols(cachedDocument.contents[0].query, textDocument.uri);
}
async handleWorkspaceSymbolRequest(params) {
if (!this._isInitialized) {
return [];
}
if (params.query !== '') {
const documents = this._getTextDocuments();
const symbols = [];
await Promise.all(documents.map(async ([uri]) => {
const cachedDocument = this._getCachedDocument(uri);
if (!cachedDocument) {
return [];
}
const docSymbols = await this._languageService.getDocumentSymbols(cachedDocument.contents[0].query, uri);
symbols.push(...docSymbols);
}));
return symbols.filter(symbol => { var _a; return (_a = symbol === null || symbol === void 0 ? void 0 : symbol.name) === null || _a === void 0 ? void 0 : _a.includes(params.query); });
}
return [];
}
_getTextDocuments() {
return Array.from(this._textDocumentCache);
}
async _cacheSchemaText(uri, text, version, project) {
try {
const contents = await this._parser(text, uri);
if (contents.length > 0) {
await this._invalidateCache({ version, uri }, uri, contents);
await this._updateObjectTypeDefinition(uri, contents, project);
}
}
catch (err) {
this._logger.error(String(err));
}
}
async _cacheSchemaFile(fileUri, project) {
try {
const matches = await (0, fast_glob_1.default)(fileUri, {
cwd: project.dirpath,
absolute: true,
});
const uri = matches[0];
let version = 1;
if (uri) {
const schemaUri = vscode_uri_1.URI.file(uri).toString();
const schemaDocument = this._getCachedDocument(schemaUri);
if (schemaDocument) {
version = schemaDocument.version++;
}
const schemaText = await (0, promises_1.readFile)(uri, 'utf-8');
await this._cacheSchemaText(schemaUri, schemaText, version);
}
}
catch (err) {
this._logger.error(String(err));
}
}
_getTmpProjectPath(project, prependWithProtocol = true, appendPath) {
const baseDir = this._graphQLCache.getGraphQLConfig().dirpath;
const workspaceName = path.basename(baseDir);
const basePath = path.join(this._tmpDirBase, workspaceName);
let projectTmpPath = path.join(basePath, 'projects', project.name);
if (!(0, node_fs_1.existsSync)(projectTmpPath)) {
(0, node_fs_1.mkdirSync)(projectTmpPath, {
recursive: true,
});
}
if (appendPath) {
projectTmpPath = path.join(projectTmpPath, appendPath);
}
if (prependWithProtocol) {
return vscode_uri_1.URI.file(path.resolve(projectTmpPath)).toString();
}
return path.resolve(projectTmpPath);
}
getCachedSchemaSettings(project) {
var _a, _b, _c;
const config = (_a = project === null || project === void 0 ? void 0 : project.extensions) === null || _a === void 0 ? void 0 : _a.languageService;
let cacheSchemaFileForLookup = true;
let schemaCacheTTL = 1000 * 30;
if ((config === null || config === void 0 ? void 0 : config.cacheSchemaFileForLookup) === false ||
((_b = this === null || this === void 0 ? void 0 : this._settings) === null || _b === void 0 ? void 0 : _b.cacheSchemaFileForLookup) === false) {
cacheSchemaFileForLookup = false;
}
if (config === null || config === void 0 ? void 0 : config.schemaCacheTTL) {
schemaCacheTTL = config.schemaCacheTTL;
}
if ((_c = this === null || this === void 0 ? void 0 : this._settings) === null || _c === void 0 ? void 0 : _c.schemaCacheTTL) {
schemaCacheTTL = this._settings.schemaCacheTTL;
}
return { cacheSchemaFileForLookup, schemaCacheTTL };
}
async _cacheSchemaFilesForProject(project) {
const { cacheSchemaFileForLookup } = this.getCachedSchemaSettings(project);
const unwrappedSchema = (0, common_1.unwrapProjectSchema)(project);
const sdlOnly = (0, common_1.isProjectSDLOnly)(unwrappedSchema);
try {
}
catch (_a) { }
if (cacheSchemaFileForLookup && !sdlOnly) {
await this.cacheConfigSchemaFile(project);
}
else if (sdlOnly) {
await Promise.all(unwrappedSchema.map(async (schemaEntry) => this._cacheSchemaFile(schemaEntry, project)));
}
}
async cacheConfigSchemaFile(project) {
try {
const schema = await this._graphQLCache.getSchema(project.name);
if (schema) {
let schemaText = (0, graphql_1.printSchema)(schema);
const uri = this._getTmpProjectPath(project, true, 'generated-schema.graphql');
const fsPath = this._getTmpProjectPath(project, false, 'generated-schema.graphql');
schemaText = `# This is an automatically generated representation of your schema.\n# Any changes to this file will be overwritten and will not be\n# reflected in the resulting GraphQL schema\n\n${schemaText}`;
const cachedSchemaDoc = this._getCachedDocument(uri);
this._graphQLCache._schemaMap.set(project.name, schema);
try {
await (0, promises_1.mkdir)(path.dirname(fsPath), { recursive: true });
}
catch (_a) { }
if (!cachedSchemaDoc) {
await (0, promises_1.writeFile)(fsPath, schemaText, {
encoding: 'utf-8',
});
await this._cacheSchemaText(uri, schemaText, 0, project);
}
if (cachedSchemaDoc) {
await (0, promises_1.writeFile)(fsPath, schemaText, 'utf-8');
await this._cacheSchemaText(uri, schemaText, cachedSchemaDoc.version++, project);
}
}
}
catch (err) {
this._logger.error(String(err));
}
}
async _cacheDocumentFilesforProject(project) {
try {
const documents = await project.getDocuments();
const documentLocations = new Set(documents
.filter(doc => doc.location && doc.rawSDL)
.map(doc => doc.location));
return Promise.all(Array.from(documentLocations).map(async (loc) => {
var _a;
let filePath = loc;
if (!path.isAbsolute(filePath)) {
filePath = path.join(project.dirpath, loc);
}
const uri = vscode_uri_1.URI.file(filePath).toString();
const fileContent = await (0, promises_1.readFile)(filePath, 'utf-8');
const contents = await this._parser(fileContent, uri);
if (!((_a = contents[0]) === null || _a === void 0 ? void 0 : _a.query)) {
return;
}
await this._updateObjectTypeDefinition(uri, contents);
await this._updateFragmentDefinition(uri, contents);
await this._invalidateCache({ version: 1, uri }, uri, contents);
}));
}
catch (err) {
this._logger.error(`invalid/unknown file in graphql config documents entry:\n '${project.documents}'`);
this._logger.error(String(err));
}
}
async _cacheAllProjectFiles(config) {
if (config === null || config === void 0 ? void 0 : config.projects) {
return Promise.all(Object.keys(config.projects).map(async (projectName) => {
var _a;
const project = config.getProject(projectName);
await this._cacheSchemaFilesForProject(project);
if ((_a = project.documents) === null || _a === void 0 ? void 0 : _a.length) {
await this._cacheDocumentFilesforProject(project);
}
else {
this._logger.warn([
`No 'documents' config found for project: ${projectName}.`,
'Fragments and query documents cannot be detected.',
'LSP server will only perform some partial validation and SDL features.',
].join('\n'));
}
}));
}
}
_isRelayCompatMode(query) {
return (query.includes('RelayCompat') || query.includes('react-relay/compat'));
}
async _updateFragmentDefinition(uri, contents) {
const project = this._graphQLCache.getProjectForFile(uri);
if (project) {
const cacheKey = this._graphQLCache._cacheKeyForProject(project);
await this._graphQLCache.updateFragmentDefinition(cacheKey, uri, contents);
}
}
async _updateSchemaIfChanged(project, uri) {
await Promise.all((0, common_1.unwrapProjectSchema)(project).map(async (schema) => {
const schemaFilePath = path.resolve(project.dirpath, schema);
const uriFilePath = vscode_uri_1.URI.parse(uri).fsPath;
if (uriFilePath === schemaFilePath) {
try {
const file = await (0, promises_1.readFile)(schemaFilePath, 'utf-8');
(0, graphql_1.parse)(file, { noLocation: true });
this._graphQLCache.invalidateSchemaCacheForProject(project);
}
catch (_a) { }
}
}));
}
async _updateObjectTypeDefinition(uri, contents, project) {
const resolvedProject = project !== null && project !== void 0 ? project : (await this._graphQLCache.getProjectForFile(uri));
if (resolvedProject) {
const cacheKey = this._graphQLCache._cacheKeyForProject(resolvedProject);
await this._graphQLCache.updateObjectTypeDefinition(cacheKey, uri, contents);
}
}
_getCachedDocument(uri) {
if (this._textDocumentCache.has(uri)) {
const cachedDocument = this._textDocumentCache.get(uri);
if (cachedDocument) {
return cachedDocument;
}
}
return null;
}
async _invalidateCache(textDocument, uri, contents) {
var _a;
if (this._textDocumentCache.has(uri)) {
const cachedDocument = this._textDocumentCache.get(uri);
if (cachedDocument &&
(textDocument === null || textDocument === void 0 ? void 0 : textDocument.version) &&
cachedDocument.version < textDocument.version) {
return this._textDocumentCache.set(uri, {
version: textDocument.version,
contents,
});
}
}
return this._textDocumentCache.set(uri, {
version: (_a = textDocument.version) !== null && _a !== void 0 ? _a : 0,
contents,
});
}
}
exports.MessageProcessor = MessageProcessor;
function processDiagnosticsMessage(results, query, range) {
const queryLines = query.split('\n');
const totalLines = queryLines.length;
const lastLineLength = queryLines[totalLines - 1].length;
const lastCharacterPosition = new graphql_language_service_1.Position(totalLines, lastLineLength);
const processedResults = results.filter(diagnostic => diagnostic.range.end.lessThanOrEqualTo(lastCharacterPosition));
if (range) {
const offset = range.start;
return processedResults.map(diagnostic => ({
...diagnostic,
range: new graphql_language_service_1.Range(new graphql_language_service_1.Position(diagnostic.range.start.line + offset.line, diagnostic.range.start.character), new graphql_language_service_1.Position(diagnostic.range.end.line + offset.line, diagnostic.range.end.character)),
}));
}
return processedResults;
}
exports.processDiagnosticsMessage = processDiagnosticsMessage;
//# sourceMappingURL=MessageProcessor.js.map