diff --git a/extension.bundle.ts b/extension.bundle.ts index 4c366b742..32459a0ba 100644 --- a/extension.bundle.ts +++ b/extension.bundle.ts @@ -73,7 +73,8 @@ export * from "./src/language/IssueKind"; export * from "./src/language/LineColPos"; export * from "./src/language/ReferenceList"; export * from "./src/language/Span"; -export { LanguageServerState, notifyTemplateGraphAvailable, stopArmLanguageServer } from "./src/languageclient/startArmLanguageServer"; +export * from "./src/languageclient/getAvailableResourceTypesAndVersions"; +export * from "./src/languageclient/startArmLanguageServer"; export * from './src/snippets/ISnippetManager'; export * from './src/snippets/SnippetManager'; export * from "./src/survey"; diff --git a/package.json b/package.json index db2934732..4537593cb 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "version": "0.15.1-alpha", "publisher": "msazurermtools", "config": { - "ARM_LANGUAGE_SERVER_NUGET_VERSION": "3.0.0-preview.21158.1" + "ARM_LANGUAGE_SERVER_NUGET_VERSION": "3.0.0-preview.21206.10" }, "categories": [ "Azure", diff --git a/src/AzureRMTools.ts b/src/AzureRMTools.ts index 1b2bebbcf..638a2583d 100644 --- a/src/AzureRMTools.ts +++ b/src/AzureRMTools.ts @@ -38,7 +38,8 @@ import { Issue, IssueSeverity } from "./language/Issue"; import * as Json from "./language/json/JSON"; import { ReferenceList } from "./language/ReferenceList"; import { Span } from "./language/Span"; -import { notifyTemplateGraphAvailable, startArmLanguageServerInBackground } from "./languageclient/startArmLanguageServer"; +import { getAvailableResourceTypesAndVersions } from './languageclient/getAvailableResourceTypesAndVersions'; +import { notifyTemplateGraphAvailable, startArmLanguageServerInBackground, waitForLanguageServerAvailable } from "./languageclient/startArmLanguageServer"; import { showInsertionContext } from "./snippets/showInsertionContext"; import { SnippetManager } from "./snippets/SnippetManager"; import { survey } from "./survey"; @@ -1193,8 +1194,12 @@ export class AzureRMTools implements IProvideOpenedDocuments { resourceCounts?: string; } & TelemetryProperties = actionContext.telemetry.properties; - const resourceCounts: Histogram = deploymentTemplate.getResourceUsage(); + await waitForLanguageServerAvailable(); + const availableResourceTypesAndVersions = await getAvailableResourceTypesAndVersions(deploymentTemplate.schemaUri ?? ''); + const [resourceCounts, invalidResourceCounts, invalidVersionCounts] = deploymentTemplate.getResourceUsage(availableResourceTypesAndVersions); properties.resourceCounts = escapeNonPaths(this.histogramToTelemetryString(resourceCounts)); + properties.invalidResources = escapeNonPaths(this.histogramToTelemetryString(invalidResourceCounts)); + properties.invalidVersions = escapeNonPaths(this.histogramToTelemetryString(invalidVersionCounts)); }); } diff --git a/src/documents/templates/DeploymentTemplateDoc.ts b/src/documents/templates/DeploymentTemplateDoc.ts index 75b416ab0..516a2a752 100644 --- a/src/documents/templates/DeploymentTemplateDoc.ts +++ b/src/documents/templates/DeploymentTemplateDoc.ts @@ -21,6 +21,7 @@ import * as Json from "../../language/json/JSON"; import { ReferenceList } from "../../language/ReferenceList"; import { ContainsBehavior, Span } from "../../language/Span"; import { CachedValue } from '../../util/CachedValue'; +import { CaseInsensitiveMap } from '../../util/CaseInsensitiveMap'; import { expectParameterDocumentOrUndefined } from '../../util/expectDocument'; import { Histogram } from '../../util/Histogram'; import { nonNullValue } from '../../util/nonNull'; @@ -409,8 +410,13 @@ export class DeploymentTemplateDoc extends DeploymentDocument { /** * Gets info about schema usage, useful for telemetry */ - public getResourceUsage(): Histogram { + public getResourceUsage( + availableResourceTypesAndVersions: CaseInsensitiveMap + ): [Histogram, Histogram, Histogram] { const resourceCounts = new Histogram(); + const invalidResourceCounts = new Histogram(); + const invalidVersionCounts = new Histogram(); + // tslint:disable-next-line: strict-boolean-expressions const apiProfileString = `(profile=${this.apiProfile || 'none'})`.toLowerCase(); @@ -418,9 +424,42 @@ export class DeploymentTemplateDoc extends DeploymentDocument { const resources: Json.ArrayValue | undefined = this.topLevelValue ? Json.asArrayValue(this.topLevelValue.getPropertyValue(templateKeys.resources)) : undefined; if (resources) { traverseResources(resources, undefined); + + if (availableResourceTypesAndVersions.size > 0) { + for (const key of resourceCounts.keys) { + if (key) { + const count = resourceCounts.getCount(key); + + let apiVersionsForType: string[] | undefined; + let apiVersion: string; + let resourceType: string; + + const match = key.match(/([a-z./0-9-]+)@([a-z0-9-]+)/); + if (match) { + resourceType = match[1]; + apiVersion = match[2]; + apiVersionsForType = availableResourceTypesAndVersions.get(resourceType); + } else { + resourceType = key; + apiVersion = ""; + } + + if (apiVersionsForType) { + // The resource type is valid. Is the apiVersion valid? + if (!apiVersionsForType.includes(apiVersion)) { + // Invalid apiVersion + invalidVersionCounts.add(key, count); + } + } else { + // The resource type is not in the list + invalidResourceCounts.add(key, count); + } + } + } + } } - return resourceCounts; + return [resourceCounts, invalidResourceCounts, invalidVersionCounts]; function traverseResources(resourcesObject: Json.ArrayValue, parentKey: string | undefined): void { for (let resource of resourcesObject.elements) { diff --git a/src/languageclient/getAvailableResourceTypesAndVersions.ts b/src/languageclient/getAvailableResourceTypesAndVersions.ts new file mode 100644 index 000000000..9909df0f4 --- /dev/null +++ b/src/languageclient/getAvailableResourceTypesAndVersions.ts @@ -0,0 +1,39 @@ +// --------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.md in the project root for license information. +// --------------------------------------------------------------------------------------------- + +import { callWithTelemetryAndErrorHandlingSync } from "vscode-azureextensionui"; +import { ext } from "../extensionVariables"; +import { waitForLanguageServerAvailable } from "../languageclient/startArmLanguageServer"; +import { CaseInsensitiveMap } from "../util/CaseInsensitiveMap"; + +/** + * Returns a case-insensitive map of available resource types and their apiVersions for a given ARM schema from the + * currently-known schema cache in the language server. + * The map is keyed by the resource type and each value is an array of valid apiVersions. + * + * @param schema The ARM schema of the template document to query for + */ +export async function getAvailableResourceTypesAndVersions(schema: string): Promise> { + const map = new CaseInsensitiveMap(); + + await callWithTelemetryAndErrorHandlingSync("getAvailableResourceTypesAndVersions", async () => { + await waitForLanguageServerAvailable(); + + const resourceTypes = <{ [key: string]: string[] }> + await ext.languageServerClient?. + sendRequest("arm-template/getAvailableResourceTypesAndVersions", { + Schema: schema + }); + + for (const entry of Object.entries(resourceTypes)) { + const key = entry[0]; + const value = entry[1].map(apiVersion => apiVersion.toLowerCase()); + + map.set(key, value); + } + }); + + return map; +} diff --git a/src/languageclient/startArmLanguageServer.ts b/src/languageclient/startArmLanguageServer.ts index 0a8f9e716..90541db4f 100644 --- a/src/languageclient/startArmLanguageServer.ts +++ b/src/languageclient/startArmLanguageServer.ts @@ -9,6 +9,7 @@ import * as path from 'path'; import { Diagnostic, Event, EventEmitter, ProgressLocation, Uri, window, workspace } from 'vscode'; import { callWithTelemetryAndErrorHandling, callWithTelemetryAndErrorHandlingSync, IActionContext, ITelemetryContext, parseError } from 'vscode-azureextensionui'; import { LanguageClient, LanguageClientOptions, RevealOutputChannelOn, ServerOptions } from 'vscode-languageclient'; +import { delay } from '../../test/support/delay'; import { acquireSharedDotnetInstallation } from '../acquisition/acquireSharedDotnetInstallation'; import { armTemplateLanguageId, backendValidationDiagnosticsSource, configKeys, configPrefix, downloadDotnetVersion, languageFriendlyName, languageServerFolderName, languageServerName, notifications } from '../constants'; import { convertDiagnosticUrisToLinkedTemplateSchema, INotifyTemplateGraphArgs, IRequestOpenLinkedFileArgs, onRequestOpenLinkedFile } from '../documents/templates/linkedTemplates/linkedTemplates'; @@ -28,12 +29,12 @@ let haveFirstSchemasFinishedLoading: boolean = false; let isShowingLoadingSchemasProgress: boolean = false; export enum LanguageServerState { - NotStarted, - Starting, - Failed, - Running, - Stopped, - LoadingSchemas, + NotStarted, // 0 + Starting, // 1 + Failed, // 2 + Running, // 3 + Stopped, // 4 + LoadingSchemas, // 5 } /** @@ -77,6 +78,8 @@ export function startArmLanguageServerInBackground(): void { assertNever(ext.languageServerState); } + ext.languageServerState = LanguageServerState.Starting; + window.withProgress( { location: ProgressLocation.Notification, @@ -86,7 +89,6 @@ export function startArmLanguageServerInBackground(): void { await callWithTelemetryAndErrorHandling('startArmLanguageServer', async (actionContext: IActionContext) => { actionContext.telemetry.suppressIfSuccessful = true; - ext.languageServerState = LanguageServerState.Starting; try { // The server is implemented in .NET Core. We run it by calling 'dotnet' with the dll as an argument let serverDllPath: string = findLanguageServer(); @@ -123,7 +125,7 @@ async function getLangServerVersion(): Promise { }); } -export async function startLanguageClient(serverDllPath: string, dotnetExePath: string): Promise { +async function startLanguageClient(serverDllPath: string, dotnetExePath: string): Promise { // tslint:disable-next-line: no-suspicious-comment // tslint:disable-next-line: max-func-body-length // TODO: Refactor function await callWithTelemetryAndErrorHandling('startArmLanguageClient', async (actionContext: IActionContext) => { @@ -216,6 +218,8 @@ export async function startLanguageClient(serverDllPath: string, dotnetExePath: }); try { + // client.trace = Trace.Messages; + let disposable = client.start(); ext.context.subscriptions.push(disposable); @@ -382,3 +386,48 @@ function showLoadingSchemasProgress(): void { }); } } + +export async function waitForLanguageServerAvailable(): Promise { + const currentState = ext.languageServerState; + switch (currentState) { + case LanguageServerState.Stopped: + case LanguageServerState.NotStarted: + case LanguageServerState.Failed: + startArmLanguageServerInBackground(); + break; + + case LanguageServerState.Running: + return; + + case LanguageServerState.LoadingSchemas: + case LanguageServerState.Starting: + break; + + default: + assertNever(currentState); + } + + // tslint:disable-next-line: no-constant-condition + while (true) { + switch (ext.languageServerState) { + case LanguageServerState.Failed: + throw new Error(`Language server failed on start-up: ${ext.languageServerStartupError}`); + case LanguageServerState.NotStarted: + case LanguageServerState.Starting: + case LanguageServerState.LoadingSchemas: + await delay(100); + break; + case LanguageServerState.Running: + await delay(1000); // Give vscode time to notice the new formatter available (I don't know of a way to detect this) + + ext.outputChannel.appendLine("Language server is ready."); + assert(ext.languageServerClient); + return; + + case LanguageServerState.Stopped: + throw new Error('Language server has stopped'); + default: + assertNever(ext.languageServerState); + } + } +} diff --git a/src/util/CaseInsensitiveMap.ts b/src/util/CaseInsensitiveMap.ts index 26eb07c0d..e461c320f 100644 --- a/src/util/CaseInsensitiveMap.ts +++ b/src/util/CaseInsensitiveMap.ts @@ -34,6 +34,10 @@ export class CaseInsensitiveMap { return casePreservedKeys.values(); } + public entries(): [TKey, TValue][] { + return <[TKey, TValue][]>Object.entries(this.map); + } + public map(callbackfn: (key: TKey, value: TValue) => TReturn): TReturn[] { const array: TReturn[] = []; this._map.forEach((entry: [TKey, TValue]) => { diff --git a/test/ResourceUsage.test.ts b/test/ResourceUsage.test.ts index 739a369a5..89c76648a 100644 --- a/test/ResourceUsage.test.ts +++ b/test/ResourceUsage.test.ts @@ -6,27 +6,49 @@ // tslint:disable:no-non-null-assertion object-literal-key-quotes variable-name import * as assert from "assert"; -import { Histogram } from "../extension.bundle"; -import { IDeploymentTemplate } from "./support/diagnostics"; +import { CaseInsensitiveMap, Histogram } from "../extension.bundle"; +import { IPartialDeploymentTemplate } from "./support/diagnostics"; import { parseTemplate } from "./support/parseTemplate"; suite("ResourceUsage (schema.stats telemetry)", () => { - async function testGetResourceUsage(template: Partial, expectedResourceUsage: { [key: string]: number }): Promise { - // tslint:disable-next-line:no-any + async function testGetResourceUsage( + template: IPartialDeploymentTemplate | string, + expectedResourceCounts: { [key: string]: number }, + expectedInvalidResourceCounts?: { [key: string]: number }, + expectedInvalidVersionCounts?: { [key: string]: number } + ): Promise { const dt = parseTemplate(template); - const resourceUsage: Histogram = dt.getResourceUsage(); - const expected = new Histogram(); - for (let propName of Object.getOwnPropertyNames(expectedResourceUsage)) { - expected.add(propName, expectedResourceUsage[propName]); + const availableResourceTypesAndVersions = new CaseInsensitiveMap(); + availableResourceTypesAndVersions.set("Microsoft.Network/virtualNetworks", ["2016-08-01", "2018-10-01", "2019-08-01"]); + availableResourceTypesAndVersions.set("Microsoft.Resources/deployments", ["2020-10-01"]); + const [resourceUsage, invalidResourceCounts, invalidVersionCounts] = dt.getResourceUsage(availableResourceTypesAndVersions); + + const expectedResourceCountsHistogram = new Histogram(); + for (let propName of Object.getOwnPropertyNames(expectedResourceCounts)) { + expectedResourceCountsHistogram.add(propName, expectedResourceCounts[propName]); + } + assert.deepStrictEqual(resourceUsage, expectedResourceCountsHistogram); + + if (expectedInvalidResourceCounts) { + const expectedInvalidResourceCountsHistogram = new Histogram(); + for (let propName of Object.getOwnPropertyNames(expectedInvalidResourceCounts)) { + expectedInvalidResourceCountsHistogram.add(propName, expectedInvalidResourceCounts[propName]); + } + assert.deepStrictEqual(invalidResourceCounts, expectedInvalidResourceCountsHistogram); + } + if (expectedInvalidVersionCounts) { + const expectedInvalidVersionCountsHistogram = new Histogram(); + for (let propName of Object.getOwnPropertyNames(expectedInvalidVersionCounts)) { + expectedInvalidVersionCountsHistogram.add(propName, expectedInvalidVersionCounts[propName]); + } + assert.deepStrictEqual(invalidVersionCounts, expectedInvalidVersionCountsHistogram); } - assert.deepStrictEqual(resourceUsage, expected); } test("Simple resources", async () => { - // tslint:disable-next-line:no-any - const template = { + const template: IPartialDeploymentTemplate = { resources: [ { "name": "[parameters('virtualMachineName')]", @@ -58,8 +80,7 @@ suite("ResourceUsage (schema.stats telemetry)", () => { }); test("Multiple uses of resources, same version", async () => { - // tslint:disable-next-line:no-any - const template = { + const template: IPartialDeploymentTemplate = { resources: [ { "name": "[parameters('virtualMachineName')]", @@ -86,8 +107,7 @@ suite("ResourceUsage (schema.stats telemetry)", () => { }); test("Multiple uses of resources, case insensitive", async () => { - // tslint:disable-next-line:no-any - const template = { + const template: IPartialDeploymentTemplate = { resources: [ { "name": "[parameters('virtualMachineName')]", @@ -114,8 +134,7 @@ suite("ResourceUsage (schema.stats telemetry)", () => { }); test("case insensitive keys", async () => { - // tslint:disable-next-line:no-any - const template = { + const template: IPartialDeploymentTemplate = { resources: [ { "name": "[parameters('virtualMachineName')]", @@ -125,15 +144,13 @@ suite("ResourceUsage (schema.stats telemetry)", () => { ] }; - // tslint:disable-next-line:no-any - await testGetResourceUsage(template, { + await testGetResourceUsage(template, { "microsoft.compute/virtualmachines@2016-04-30-preview": 1 }); }); test("Multiple uses of resources, different version", async () => { - // tslint:disable-next-line:no-any - const template = { + const template: IPartialDeploymentTemplate = { resources: [ { "name": "[parameters('virtualMachineName')]", @@ -161,8 +178,7 @@ suite("ResourceUsage (schema.stats telemetry)", () => { }); test("Child resources", async () => { - // tslint:disable-next-line:no-any - const template = { + const template: IPartialDeploymentTemplate = { resources: [ { "apiVersion": "2018-10-01", @@ -201,8 +217,7 @@ suite("ResourceUsage (schema.stats telemetry)", () => { }); test("Deep child resources", async () => { - // tslint:disable-next-line:no-any - const template = { + const template: IPartialDeploymentTemplate = { resources: [ { "apiVersion": "2018-10-01", @@ -265,8 +280,7 @@ suite("ResourceUsage (schema.stats telemetry)", () => { }); test("resource type is expression", async () => { - // tslint:disable-next-line:no-any - const template = { + const template: IPartialDeploymentTemplate = { resources: [ { "name": "[parameters('virtualMachineName')]", @@ -276,15 +290,13 @@ suite("ResourceUsage (schema.stats telemetry)", () => { ] }; - // tslint:disable-next-line:no-any - await testGetResourceUsage(template, { + await testGetResourceUsage(template, { "[expression]@2016-04-30-preview": 1 }); }); test("apiVersion is expression", async () => { - // tslint:disable-next-line:no-any - const template = { + const template: IPartialDeploymentTemplate = { resources: [ { "apiVersion": "[concat(variables('apiVersion),'-preview')]", @@ -293,14 +305,12 @@ suite("ResourceUsage (schema.stats telemetry)", () => { ] }; - // tslint:disable-next-line:no-any - await testGetResourceUsage(template, { + await testGetResourceUsage(template, { "microsoft.network/virtualnetworks@[expression]": 1 }); }); test("apiVersion is multi-line expression", async () => { - // tslint:disable-next-line:no-any const template = `{ "resources": [ { @@ -311,15 +321,13 @@ suite("ResourceUsage (schema.stats telemetry)", () => { ] }`; - // tslint:disable-next-line:no-any - await testGetResourceUsage(template, { + await testGetResourceUsage(template, { "microsoft.network/virtualnetworks@[expression]": 1 }); }); test("apiVersion is missing, no apiProfile specified)", async () => { - // tslint:disable-next-line:no-any - const template = { + const template: IPartialDeploymentTemplate = { resources: [ { "type": "Microsoft.Network/virtualNetworks" @@ -327,15 +335,13 @@ suite("ResourceUsage (schema.stats telemetry)", () => { ] }; - // tslint:disable-next-line:no-any - await testGetResourceUsage(template, { + await testGetResourceUsage(template, { "microsoft.network/virtualnetworks@(profile=none)": 1 }); }); test("apiVersion is missing, apiProfile specified)", async () => { - // tslint:disable-next-line:no-any - const template = { + const template: IPartialDeploymentTemplate = { apiProfile: "myProfile", resources: [ { @@ -344,9 +350,64 @@ suite("ResourceUsage (schema.stats telemetry)", () => { ] }; - // tslint:disable-next-line:no-any - await testGetResourceUsage(template, { + await testGetResourceUsage(template, { "microsoft.network/virtualnetworks@(profile=myprofile)": 1 }); }); + + test("Invalid resource types and apiVersions)", async () => { + const template: IPartialDeploymentTemplate = { + resources: [ + { + "apiVersion": "2018-10-01", + "type": "Microsoft.Network/virtualNetworks", + }, + { + "apiVersion": "2018-10-01", + "type": "Microsoft.Network/virtualNetworks", + }, + + // Invalid api version + { + "apiVersion": "2018-10-99", + "type": "Microsoft.Network/virtualNetworks" + }, + { + "apiVersion": "2018-10-99", + "type": "Microsoft.Network/virtualNetworks" + }, + + // Invalid type + { + "apiVersion": "2018-10-01", + "type": "Microsoft.Network/virtualNetworkies", + }, + { + "apiVersion": "2018-10-01", + "type": "Microsoft.Network/virtualNetworkies", + }, + { + "apiVersion": "2018-10-01", + "type": "Microsoft.Network/virtualNetworkies", + } + + ] + }; + + await testGetResourceUsage( + template, + { + "microsoft.network/virtualnetworks@2018-10-01": 2, + "microsoft.network/virtualnetworkies@2018-10-01": 3, + "microsoft.network/virtualnetworks@2018-10-99": 2, + }, + { + "microsoft.network/virtualnetworkies@2018-10-01": 3 + }, + { + "microsoft.network/virtualnetworks@2018-10-99": 2 + } + ); + }); + }); diff --git a/test/functional/getAvailableResourceTypesAndVersions.test.ts b/test/functional/getAvailableResourceTypesAndVersions.test.ts new file mode 100644 index 000000000..994c586a4 --- /dev/null +++ b/test/functional/getAvailableResourceTypesAndVersions.test.ts @@ -0,0 +1,49 @@ +// tslint:disable:no-useless-files +// --------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.md in the project root for license information. +// --------------------------------------------------------------------------------------------- + +// tslint:disable-next-line: no-suspicious-comment +// TODO: Gives this on build machine only: Error: "registerUIExtensionVariables" must be called before using the vscode-azureextensionui package. +/* +import * as assert from "assert"; +import { getAvailableResourceTypesAndVersions } from "../../extension.bundle"; +import { ensureLanguageServerAvailable } from "../support/ensureLanguageServerAvailable"; +import { testWithLanguageServer } from "../support/testWithLanguageServer"; + +suite("getAvailableResourceTypesAndVersions", () => { + testWithLanguageServer("deploymentTemplate.json#", async () => { + await ensureLanguageServerAvailable(); + const resourceTypes = await getAvailableResourceTypesAndVersions("https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#"); + assert(resourceTypes.size >= 1000, "Should be lots of resource types"); + assert(resourceTypes.get("microsoft.ApiManagement/service/users")); + assert(resourceTypes.get("microsoft.ApiManagement/service/users")?.includes('2018-01-01')); + for (const rt of resourceTypes.entries()) { + assert(rt.length > 0, "Each type listed should have at least one apiVersion"); + } + }); + + testWithLanguageServer("managementGroupDeploymentTemplate.json", async () => { + await ensureLanguageServerAvailable(); + const resourceTypes = await getAvailableResourceTypesAndVersions("https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#"); + assert(Object.entries(resourceTypes).length >= 10); + for (const rt of resourceTypes.entries()) { + assert(rt.length > 0, "Each type listed should have at least one apiVersion"); + } + }); + + testWithLanguageServer("unknown schema", async () => { + await ensureLanguageServerAvailable(); + const resourceTypes = await getAvailableResourceTypesAndVersions("https://schema.management.azure.com/schemas/unknown/deploymentTemplate.json#"); + assert.equal(resourceTypes.size, 0); + }); + + testWithLanguageServer("schema without hash = with hash", async () => { + await ensureLanguageServerAvailable(); + const resourceTypes1 = await getAvailableResourceTypesAndVersions("https://schema.management.azure.com/schemas/unknown/deploymentTemplate.json#"); + const resourceTypes2 = await getAvailableResourceTypesAndVersions("https://schema.management.azure.com/schemas/unknown/deploymentTemplate.json"); + assert.equal(resourceTypes1.size, resourceTypes2.size); + }); +}); +*/ diff --git a/test/support/ensureLanguageServerAvailable.ts b/test/support/ensureLanguageServerAvailable.ts index bc3a944b6..33dd19b05 100644 --- a/test/support/ensureLanguageServerAvailable.ts +++ b/test/support/ensureLanguageServerAvailable.ts @@ -5,9 +5,8 @@ import * as assert from "assert"; import { workspace } from "vscode"; import { LanguageClient } from "vscode-languageclient"; -import { armTemplateLanguageId, assertNever, ext, LanguageServerState } from "../../extension.bundle"; +import { armTemplateLanguageId, ext, waitForLanguageServerAvailable } from "../../extension.bundle"; import { DISABLE_LANGUAGE_SERVER } from "../testConstants"; -import { delay } from "./delay"; import { testLog } from "./testLog"; let isLanguageServerAvailable = false; @@ -26,29 +25,8 @@ export async function ensureLanguageServerAvailable(): Promise { language: armTemplateLanguageId, }); - // tslint:disable-next-line: no-constant-condition - while (!isLanguageServerAvailable) { - switch (ext.languageServerState) { - case LanguageServerState.Failed: - throw new Error(`Language server failed on start-up: ${ext.languageServerStartupError}`); - case LanguageServerState.NotStarted: - case LanguageServerState.Starting: - case LanguageServerState.LoadingSchemas: - await delay(100); - break; - case LanguageServerState.Running: - await delay(1000); // Give vscode time to notice the new formatter available (I don't know of a way to detect this) - - isLanguageServerAvailable = true; - testLog.writeLine("Language server now available"); - break; - - case LanguageServerState.Stopped: - throw new Error('Language server stopped'); - default: - assertNever(ext.languageServerState); - } - } + await waitForLanguageServerAvailable(); + testLog.writeLine("Language server now available"); } assert(ext.languageServerClient);