@microsoft/compose-language-service
Version:
Language service for Docker Compose documents
252 lines • 12.2 kB
JavaScript
"use strict";
/*!--------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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 () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ComposeLanguageService = void 0;
const util = __importStar(require("util"));
const vscode_languageserver_1 = require("vscode-languageserver");
const DocumentSettingsClientCapabilities_1 = require("../common/DocumentSettingsClientCapabilities");
const TelemetryEvent_1 = require("../common/TelemetryEvent");
const ComposeDocument_1 = require("./ComposeDocument");
const MultiCompletionProvider_1 = require("./providers/completion/MultiCompletionProvider");
const DiagnosticProvider_1 = require("./providers/DiagnosticProvider");
const DocumentFormattingProvider_1 = require("./providers/DocumentFormattingProvider");
const ImageLinkProvider_1 = require("./providers/ImageLinkProvider");
const KeyHoverProvider_1 = require("./providers/KeyHoverProvider");
const ServiceStartupCodeLensProvider_1 = require("./providers/ServiceStartupCodeLensProvider");
const ActionContext_1 = require("./utils/ActionContext");
const TelemetryAggregator_1 = require("./utils/telemetry/TelemetryAggregator");
const DefaultCapabilities = {
// Text document synchronization
textDocumentSync: {
openClose: true,
change: vscode_languageserver_1.TextDocumentSyncKind.Incremental,
willSave: false,
willSaveWaitUntil: false,
save: false,
},
// Both basic and advanced completions
completionProvider: {
triggerCharacters: ['-', ':', ' ', '"'],
resolveProvider: false,
},
// Code lenses for starting services
codeLensProvider: {
resolveProvider: false,
},
// Hover over YAML keys
hoverProvider: true,
// Links to Docker Hub on image names
documentLinkProvider: {
resolveProvider: false,
},
// YAML formatting
documentFormattingProvider: true,
// Workspace features
workspace: {
workspaceFolders: {
supported: true,
},
},
};
// Default settings for a client with no alternate YAML language service
const DefaultAlternateYamlLanguageServiceClientCapabilities = {
syntaxValidation: false,
schemaValidation: false,
basicCompletions: false,
advancedCompletions: false,
serviceStartupCodeLens: false,
hover: false,
imageLinks: false,
formatting: false,
};
class ComposeLanguageService {
connection;
clientParams;
documentManager = new vscode_languageserver_1.TextDocuments(ComposeDocument_1.ComposeDocument.DocumentManagerConfig);
subscriptions = [];
telemetryAggregator;
_capabilities = DefaultCapabilities;
constructor(connection, clientParams) {
this.connection = connection;
this.clientParams = clientParams;
let altYamlCapabilities = clientParams.capabilities.experimental?.alternateYamlLanguageService;
if (altYamlCapabilities) {
connection.console.info('An alternate YAML language service is present. The Compose language service will not enable features already provided by the alternate.');
}
else {
altYamlCapabilities = DefaultAlternateYamlLanguageServiceClientCapabilities;
}
// Hook up the document listeners, which create a Disposable which will be added to this.subscriptions
if (altYamlCapabilities.syntaxValidation && altYamlCapabilities.schemaValidation) {
// Noop. No server-side capability needs to be set for diagnostics because it is based on pushing from server to client.
}
else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
this.createDocumentManagerHandler(this.documentManager.onDidChangeContent, new DiagnosticProvider_1.DiagnosticProvider(clientParams.initializationOptions?.diagnosticDelay, !altYamlCapabilities.syntaxValidation, !altYamlCapabilities.schemaValidation));
}
// End of document listeners
// Hook up all the applicable LSP listeners, which do not create Disposables for some reason
if (altYamlCapabilities.basicCompletions && altYamlCapabilities.advancedCompletions) {
this._capabilities.completionProvider = undefined;
}
else {
this.createLspHandler(this.connection.onCompletion.bind(this), new MultiCompletionProvider_1.MultiCompletionProvider(!altYamlCapabilities.basicCompletions, !altYamlCapabilities.advancedCompletions));
}
if (altYamlCapabilities.serviceStartupCodeLens) {
this._capabilities.codeLensProvider = undefined;
}
else {
this.createLspHandler(this.connection.onCodeLens.bind(this), new ServiceStartupCodeLensProvider_1.ServiceStartupCodeLensProvider());
}
if (altYamlCapabilities.hover) {
this._capabilities.hoverProvider = undefined;
}
else {
this.createLspHandler(this.connection.onHover.bind(this), new KeyHoverProvider_1.KeyHoverProvider());
}
if (altYamlCapabilities.imageLinks) {
this._capabilities.documentLinkProvider = undefined;
}
else {
this.createLspHandler(this.connection.onDocumentLinks.bind(this), new ImageLinkProvider_1.ImageLinkProvider());
}
if (altYamlCapabilities.formatting) {
this._capabilities.documentFormattingProvider = undefined;
}
else {
this.createLspHandler(this.connection.onDocumentFormatting.bind(this), new DocumentFormattingProvider_1.DocumentFormattingProvider());
}
// End of LSP listeners
// Hook up one additional notification handler
this.connection.onNotification(DocumentSettingsClientCapabilities_1.DocumentSettingsNotification.type, (params) => this.onDidChangeDocumentSettings(params));
// Start the document listener
this.documentManager.listen(this.connection);
// Start the telemetry aggregator
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
this.subscriptions.push(this.telemetryAggregator = new TelemetryAggregator_1.TelemetryAggregator(this.connection, clientParams.initializationOptions?.telemetryAggregationInterval));
}
dispose() {
for (const subscription of this.subscriptions) {
subscription.dispose();
}
}
get capabilities() {
return this._capabilities;
}
onDidChangeDocumentSettings(params) {
// TODO: Telemetrize this?
const composeDoc = this.documentManager.get(params.textDocument.uri);
if (composeDoc) {
composeDoc.updateSettings(params);
}
}
createLspHandler(event,
// The result (`R`) and error (`E`) types are inferred from the connection's
// handler signature (via `NoInfer`) and the provider is validated against them.
// The partial-result type (`PR`) is not cross-checked: the LSP connection types it
// as the item type while providers use `never`, and those are contravariantly
// incompatible even though the wrapper simply forwards the reporter unchanged.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
handler) {
event(async (params, token, workDoneProgress, resultProgress) => {
return await this.callWithTelemetryAndErrorHandling(handler.constructor.name, async () => {
const doc = this.documentManager.get(params.textDocument.uri);
if (!doc) {
throw new vscode_languageserver_1.ResponseError(vscode_languageserver_1.ErrorCodes.InvalidParams, 'Document not found in cache.');
}
const extendedParams = {
...params,
document: doc,
};
return await Promise.resolve(handler.on(extendedParams, token, workDoneProgress, resultProgress));
});
});
}
createDocumentManagerHandler(event, handler) {
event(async (params) => {
return await this.callWithTelemetryAndErrorHandling(handler.constructor.name, async () => {
const extendedParams = {
...params,
textDocument: params.document.id,
};
return await Promise.resolve(handler.on(extendedParams, vscode_languageserver_1.CancellationToken.None));
});
}, this, this.subscriptions);
}
async callWithTelemetryAndErrorHandling(callbackId, callback) {
const actionContext = {
clientCapabilities: this.clientParams.capabilities,
connection: this.connection,
telemetry: (0, TelemetryEvent_1.initEvent)(callbackId),
};
const startTime = process.hrtime.bigint();
try {
return await (0, ActionContext_1.runWithContext)(actionContext, callback);
}
catch (error) {
let responseError;
let stack;
if (error instanceof vscode_languageserver_1.ResponseError) {
responseError = error;
stack = error.stack;
}
else if (error instanceof Error) {
responseError = new vscode_languageserver_1.ResponseError(vscode_languageserver_1.ErrorCodes.UnknownErrorCode, error.message, error);
stack = error.stack;
}
else {
responseError = new vscode_languageserver_1.ResponseError(vscode_languageserver_1.ErrorCodes.InternalError, util.inspect(error) || 'Unknown error');
}
actionContext.telemetry.properties.result = 'Failed';
actionContext.telemetry.properties.error = responseError.code.toString();
actionContext.telemetry.properties.errorMessage = responseError.message;
actionContext.telemetry.properties.stack = stack;
return responseError;
}
finally {
const endTime = process.hrtime.bigint();
const elapsedMicroseconds = Number((endTime - startTime) / BigInt(1000));
actionContext.telemetry.measurements.duration = elapsedMicroseconds;
// The aggregator will internally handle suppressing / etc.
this.telemetryAggregator.logEvent(actionContext.telemetry);
}
}
}
exports.ComposeLanguageService = ComposeLanguageService;
//# sourceMappingURL=ComposeLanguageService.js.map