From 229fd2bb26a9f824729dfc2f282a1bb46d2e65fd Mon Sep 17 00:00:00 2001 From: Albert Li Date: Mon, 27 Mar 2023 23:56:48 +0800 Subject: [PATCH 1/3] feat. support version tag p1 --- package.json | 77 ++++++++---- src/GlobalEventBus.ts | 14 +++ src/commands/LifecycleCommands.ts | 114 +++++++++++------ src/common/PqSdkNugetPackageService.ts | 32 ++++- src/common/nuget/NugetCommandService.ts | 6 +- src/common/nuget/NugetLiteHttpService.ts | 6 +- src/constants/PowerQuerySdkConfiguration.ts | 36 +++++- src/constants/PowerQuerySdkExtension.ts | 3 + src/utils/NugetVersions.ts | 42 +++++++ src/utils/assertUtils.ts | 14 +++ unit-tests/common/nuget/NugetVersions.spec.ts | 119 ++++++++++++++++++ 11 files changed, 396 insertions(+), 67 deletions(-) create mode 100644 src/utils/assertUtils.ts diff --git a/package.json b/package.json index 54247c94..d50676f5 100644 --- a/package.json +++ b/package.json @@ -116,74 +116,109 @@ "configuration": { "title": "Power Query SDK", "properties": { + "powerquery.sdk.features.autoDetection": { + "scope": "machine-overridable", + "type": "boolean", + "description": "%extension.pqtest.config.features.autoDetection.description%", + "default": true, + "order": 9 + }, "powerquery.sdk.project.autoDetection": { "scope": "machine-overridable", "type": "boolean", "description": "%extension.pqtest.config.features.autoDetection.description%", "default": true, + "order": 10, "deprecationMessage": "Deprecated: Please use powerquery.sdk.features.autoDetection instead.", "markdownDeprecationMessage": "**Deprecated**: Please use `#powerquery.sdk.features.autoDetection#` instead." }, - "powerquery.sdk.pqtest.location": { + "powerquery.sdk.features.useServiceHost": { "scope": "machine-overridable", + "type": "boolean", + "default": false, + "order": 10, + "description": "%extension.pqtest.config.features.useServiceHost%" + }, + "powerquery.sdk.defaultExtension": { + "scope": "window", "type": "string", - "deprecationMessage": "Deprecated: Please use powerquery.sdk.tools.location instead.", - "description": "%extension.pqtest.config.pqtest.location.description%" + "order": 29, + "description": "%extension.pqtest.config.pqtest.extension.description%" + }, + "powerquery.sdk.defaultQueryFile": { + "scope": "window", + "type": "string", + "order": 29, + "description": "%extension.pqtest.config.pqtest.queryFile.description%" }, "powerquery.sdk.pqtest.extension": { "scope": "window", "type": "string", + "order": 30, "deprecationMessage": "Deprecated: Please use powerquery.sdk.defaultExtension instead.", "description": "%extension.pqtest.config.pqtest.extension.description%" }, "powerquery.sdk.pqtest.queryFile": { "scope": "window", "type": "string", + "order": 30, "deprecationMessage": "Deprecated: Please use powerquery.sdk.defaultQueryFile instead.", "description": "%extension.pqtest.config.pqtest.queryFile.description%" }, "powerquery.sdk.externals.msbuildPath": { "scope": "machine-overridable", "type": "string", + "order": 50, "description": "%extension.pqtest.config.externals.msbuildPath.description%" }, "powerquery.sdk.externals.nugetPath": { "scope": "machine-overridable", "type": "string", + "order": 50, "description": "%extension.pqtest.config.externals.nugetPath.description%" }, + "powerquery.sdk.externals.versionTag": { + "scope": "machine-overridable", + "type": "string", + "enum": [ + "Recommended", + "Latest", + "Customized" + ], + "enumDescriptions": [ + "The stable version", + "The latest version, working only with explict nuget path", + "Use a user provided version value" + ], + "description": "The version tag of the external tools to be seized.", + "default": "Recommended", + "order": 50 + }, "powerquery.sdk.externals.nugetFeed": { "scope": "window", "type": "string", + "order": 50, "description": "%extension.pqtest.config.externals.nugetFeed.description%", "default": "" }, "powerquery.sdk.tools.location": { "scope": "machine-overridable", "type": "string", + "order": 69, "description": "%extension.pqtest.config.pqtest.location.description%" }, - "powerquery.sdk.defaultExtension": { - "scope": "window", - "type": "string", - "description": "%extension.pqtest.config.pqtest.extension.description%" - }, - "powerquery.sdk.defaultQueryFile": { - "scope": "window", - "type": "string", - "description": "%extension.pqtest.config.pqtest.queryFile.description%" - }, - "powerquery.sdk.features.autoDetection": { + "powerquery.sdk.pqtest.location": { "scope": "machine-overridable", - "type": "boolean", - "description": "%extension.pqtest.config.features.autoDetection.description%", - "default": true + "type": "string", + "order": 70, + "deprecationMessage": "Deprecated: Please use powerquery.sdk.tools.location instead.", + "description": "%extension.pqtest.config.pqtest.location.description%" }, - "powerquery.sdk.features.useServiceHost": { + "powerquery.sdk.tools.version": { "scope": "machine-overridable", - "type": "boolean", - "default": false, - "description": "%extension.pqtest.config.features.useServiceHost%" + "type": "string", + "order": 71, + "description": "The current version of the pqTest." } } }, diff --git a/src/GlobalEventBus.ts b/src/GlobalEventBus.ts index e9367af1..71fb6ce3 100644 --- a/src/GlobalEventBus.ts +++ b/src/GlobalEventBus.ts @@ -35,6 +35,8 @@ export const GlobalEvents = Object.freeze({ ConfigDidChangePowerQueryTestLocation: "ConfigDidChangePowerQueryTestLocation" as const, ConfigDidChangePQTestExtension: "ConfigDidChangePQTestExtension" as const, ConfigDidChangePQTestQuery: "ConfigDidChangePQTestQuery" as const, + ConfigDidChangeExternalVersionTag: "ConfigDidChangeExternalVersionTag" as const, + ConfigDidChangePqTestVersion: "ConfigDidChangePqTestVersion" as const, }), }); type GlobalEventTypes = ExtractEventTypes; @@ -122,6 +124,18 @@ export class GlobalEventBus extends DisposableEventEmitter imp void vscode.commands.executeCommand("workbench.action.reloadWindow"); } })(); + } else if ( + evt.affectsConfiguration( + `${ExtensionConstants.ConfigNames.PowerQuerySdk.name}.${ExtensionConstants.ConfigNames.PowerQuerySdk.properties.externalsVersionTag}`, + ) + ) { + this.emit(GlobalEvents.VSCodeEvents.ConfigDidChangeExternalVersionTag); + } else if ( + evt.affectsConfiguration( + `${ExtensionConstants.ConfigNames.PowerQuerySdk.name}.${ExtensionConstants.ConfigNames.PowerQuerySdk.properties.pqTestVersion}`, + ) + ) { + this.emit(GlobalEvents.VSCodeEvents.ConfigDidChangePqTestVersion); } } else if (evt.affectsConfiguration(ExtensionConstants.ConfigNames.PowerQuery.name)) { if ( diff --git a/src/commands/LifecycleCommands.ts b/src/commands/LifecycleCommands.ts index f4870fe0..84ccb74c 100644 --- a/src/commands/LifecycleCommands.ts +++ b/src/commands/LifecycleCommands.ts @@ -19,8 +19,6 @@ import { WorkspaceFolder, } from "vscode"; -import { GlobalEventBus } from "../GlobalEventBus"; - import { AuthenticationKind, CreateAuthState, @@ -37,6 +35,7 @@ import { substitutedWorkspaceFolderBasenameIfNeeded, updateCurrentLocalPqModeIfNeeded, } from "../utils/vscodes"; +import { GlobalEventBus, GlobalEvents } from "../GlobalEventBus"; import { InputStep, MultiStepInput } from "../common/MultiStepInput"; import { PqServiceHostClient, PqServiceHostServerNotReady } from "../pqTestConnector/PqServiceHostClient"; import { PqTestResultViewPanel, SimplePqTestResultViewBroker } from "../panels/PqTestResultViewPanel"; @@ -71,6 +70,7 @@ export class LifecycleCommands implements IDisposable { private isSuggestingSetupCurrentWorkspace: boolean = false; private readonly initPqSdkTool$deferred: Promise; private checkAndTryToUpdatePqTestDeferred$: Promise | undefined; + private currentPqTestVersion: string = ExtensionConstants.SuggestedPqTestNugetVersion; constructor( private readonly vscExtCtx: ExtensionContext, @@ -79,6 +79,32 @@ export class LifecycleCommands implements IDisposable { private readonly pqTestService: IPQTestService, private readonly outputChannel: PqSdkOutputChannel, ) { + globalEventBus.on(GlobalEvents.VSCodeEvents.ConfigDidChangeExternalVersionTag, () => { + // when externalVersionTag changed, we need to re-invoke manuallyUpdatePqTest, + // and it will + // re-infer the version we expected + // stop the exiting running one if any + // start the new one if needed + void this.manuallyUpdatePqTest(); + }); + + globalEventBus.on(GlobalEvents.VSCodeEvents.ConfigDidChangePqTestVersion, () => { + // when PqTestVersion changed in the mode non-customized version tag, users might have it updated + // thus we need to compare it with current expected version and reset it back the one we expected + if ( + ExtensionConfigurations.externalsVersionTag !== "Customized" && + this.currentPqTestVersion !== ExtensionConfigurations.PQTestVersion + ) { + void ExtensionConfigurations.setPQTestVersion(this.currentPqTestVersion); + } else if ( + ExtensionConfigurations.externalsVersionTag == "Customized" && + this.currentPqTestVersion !== ExtensionConfigurations.PQTestVersion + ) { + // need to trigger debounced manuallyUpdatePqTest + void this.debouncedManuallyUpdatePqTest(); + } + }); + vscExtCtx.subscriptions.push( vscode.commands.registerCommand(LifecycleCommands.SeizePqTestCommand, this.manuallyUpdatePqTest.bind(this)), vscode.commands.registerCommand( @@ -167,7 +193,7 @@ export class LifecycleCommands implements IDisposable { } private intervalTask(): void { - // this task gonna be invoked repeatedly, thus make sure it is as lite as possible + // this task would be invoked repeatedly, thus make sure it is as lite as possible void this.promptSettingIncorrectOrInvokeInfoTaskIfNeeded(); } @@ -486,21 +512,25 @@ export class LifecycleCommands implements IDisposable { private async doCheckAndTryToUpdatePqTest(skipQueryDialog: boolean = false): Promise { try { - let pqTestLocation: string | undefined = ExtensionConfigurations.PQTestLocation; + const pqTestLocation: string | undefined = ExtensionConfigurations.PQTestLocation; const maybeNewVersion: string | undefined = await this.pqSdkNugetPackageService.findNullableNewPqSdkVersion(); + // have to use || over here, we want to turn empty string into a valid version + // '' ?? other -> '', '' || other -> other + const theNextVersion: string = maybeNewVersion || ExtensionConstants.SuggestedPqTestNugetVersion; + // we should not update to the latest unless the latest nuget doesn't exist on start // users might just want to use the previous one purposely // therefore do not try to update when, like, pqTestLocation.indexOf(maybeNewVersion) === -1 if ( !pqTestLocation || !this.pqTestService.pqTestReady || - !this.pqSdkNugetPackageService.nugetPqSdkExistsSync(maybeNewVersion) + !this.pqSdkNugetPackageService.nugetPqSdkExistsSync(theNextVersion) ) { - const pqTestExecutableFullPath: string | undefined = - await this.pqSdkNugetPackageService.updatePqSdkFromNuget(maybeNewVersion); + let pqTestExecutableFullPath: string | undefined = + await this.pqSdkNugetPackageService.updatePqSdkFromNuget(theNextVersion); if (!pqTestExecutableFullPath && !skipQueryDialog) { const pqTestLocationUrls: Uri[] | undefined = await vscode.window.showOpenDialog({ @@ -514,22 +544,12 @@ export class LifecycleCommands implements IDisposable { }); if (pqTestLocationUrls?.[0]) { - pqTestLocation = pqTestLocationUrls[0].fsPath; + pqTestExecutableFullPath = pqTestLocationUrls[0].fsPath; } } if (pqTestExecutableFullPath) { - // convert pqTestLocation of exe to its dirname - pqTestLocation = path.dirname(pqTestExecutableFullPath); - const histPqTestLocation: string | undefined = ExtensionConfigurations.PQTestLocation; - const newPqTestLocation: string = pqTestLocation; - - await ExtensionConfigurations.setPQTestLocation(newPqTestLocation); - - if (histPqTestLocation === newPqTestLocation) { - // update the pqtest location by force in case it equals the previous one - this.pqTestService.onPowerQueryTestLocationChanged(); - } + await this.doUpdatePqTestLocationAndStartItIfNeeded(pqTestExecutableFullPath, theNextVersion); } } @@ -552,7 +572,9 @@ export class LifecycleCommands implements IDisposable { /** * check and only update pqTest if needed like: not ready, not existing, the latest one doesn't exist either - * @param skipQueryDialog + * and this method should be invoked only once + * + * @param skipQueryDialog skip to pop up a dialog to let users fill in the pqTest.exe path * @private */ private checkAndTryToUpdatePqTest(skipQueryDialog: boolean = false): Promise { @@ -563,8 +585,28 @@ export class LifecycleCommands implements IDisposable { return this.checkAndTryToUpdatePqTestDeferred$; } + private async doUpdatePqTestLocationAndStartItIfNeeded( + pqTestExecutableFullPath: string, + theNextVersion: string, + ): Promise { + // convert pqTestLocation of exe to its dirname + const newPqTestLocation: string = path.dirname(pqTestExecutableFullPath); + const histPqTestLocation: string | undefined = ExtensionConfigurations.PQTestLocation; + + await ExtensionConfigurations.setPQTestLocation(newPqTestLocation); + this.currentPqTestVersion = theNextVersion; + await ExtensionConfigurations.setPQTestVersion(theNextVersion); + + if (histPqTestLocation === newPqTestLocation) { + // update the pqtest location by force in case it equals the previous one + this.pqTestService.onPowerQueryTestLocationChanged(); + } + } + /** - * eagerly update the pqTest as long as currently it is not configured to the latest + * Eagerly update the pqTest as long as currently it is not configured to the latest + * This method could be invoked multiple times instead + * * @param maybeNextVersion */ public async manuallyUpdatePqTest(maybeNextVersion?: string): Promise { @@ -573,32 +615,27 @@ export class LifecycleCommands implements IDisposable { maybeNextVersion = await this.pqSdkNugetPackageService.findNullableNewPqSdkVersion(); } - let pqTestLocation: string | undefined = ExtensionConfigurations.PQTestLocation; + // have to use || over here, we want to turn empty string into a valid version + // '' ?? other -> '', '' || other -> other + const theNextVersion: string = maybeNextVersion || ExtensionConstants.SuggestedPqTestNugetVersion; + + const pqTestLocation: string | undefined = ExtensionConfigurations.PQTestLocation; // determine whether we should trigger to seize or not if ( - !this.pqSdkNugetPackageService.nugetPqSdkExistsSync(maybeNextVersion) || + !this.pqSdkNugetPackageService.nugetPqSdkExistsSync(theNextVersion) || !pqTestLocation || // when manually update, we should eagerly update as long as current path is not of the latest version // like, // users might want to switch back to the latest some time after // they temporarily switch back to the previous version - (maybeNextVersion && pqTestLocation.indexOf(maybeNextVersion) === -1) + pqTestLocation.indexOf(theNextVersion) === -1 ) { const pqTestExecutableFullPath: string | undefined = - await this.pqSdkNugetPackageService.updatePqSdkFromNuget(maybeNextVersion); + await this.pqSdkNugetPackageService.updatePqSdkFromNuget(theNextVersion); if (pqTestExecutableFullPath) { - pqTestLocation = path.dirname(pqTestExecutableFullPath); - const histPqTestLocation: string | undefined = ExtensionConfigurations.PQTestLocation; - const newPqTestLocation: string = pqTestLocation; - - await ExtensionConfigurations.setPQTestLocation(newPqTestLocation); - - if (histPqTestLocation === newPqTestLocation) { - // update the pqtest location by force in case it equals the previous one - this.pqTestService.onPowerQueryTestLocationChanged(); - } + await this.doUpdatePqTestLocationAndStartItIfNeeded(pqTestExecutableFullPath, theNextVersion); } } @@ -641,6 +678,11 @@ export class LifecycleCommands implements IDisposable { return undefined; } + public debouncedManuallyUpdatePqTest: (maybeNextVersion?: string) => Promise = debounce( + (maybeNextVersion?: string) => this.manuallyUpdatePqTest(maybeNextVersion), + 2e3, + ).bind(this) as typeof this.manuallyUpdatePqTest; + public async generateOneNewProject(): Promise { const newProjName: string | undefined = await vscode.window.showInputBox({ title: extensionI18n["PQSdk.lifecycle.command.new.project.title"], @@ -660,7 +702,7 @@ export class LifecycleCommands implements IDisposable { const firstWorkspaceFolder: WorkspaceFolder | undefined = getFirstWorkspaceFolder(); if (firstWorkspaceFolder) { - // we gotta workspace and let's generate files into the first workspace + // we got the workspace and let's generate files into the first workspace const targetFolder: string = this.doGenerateOneProjectIntoOneFolderFromTemplates( firstWorkspaceFolder.uri.fsPath, newProjName, diff --git a/src/common/PqSdkNugetPackageService.ts b/src/common/PqSdkNugetPackageService.ts index c7cfb1ce..0bbfe2a4 100644 --- a/src/common/PqSdkNugetPackageService.ts +++ b/src/common/PqSdkNugetPackageService.ts @@ -48,7 +48,9 @@ export class PqSdkNugetPackageService { /** * Return a list of nuget versions less or equal to `options.maximumNugetVersion` if it got specified - * Otherwise, return the whole list. + * Otherwise, + * return the latest version in `Latest` version tag mode + * return the closest version in `Customized` version tag mode * @param options */ public async findNullableNewPqSdkVersion( @@ -58,9 +60,15 @@ export class PqSdkNugetPackageService { ): Promise { let sortedNugetVersions: NugetVersions[]; - // always restrain the version found beneath the MaximumPqTestNugetVersion - if (!options.maximumNugetVersion) { - options.maximumNugetVersion = this.nullableMaximumPqTestNugetVersion; + if (ExtensionConfigurations.externalsVersionTag === "Recommended") { + // force limiting the maximum versions of the nuget list result + if (!options.maximumNugetVersion) { + // always restrain the version found beneath the MaximumPqTestNugetVersion + options.maximumNugetVersion = this.nullableMaximumPqTestNugetVersion; + } + } else { + // in other cases, always force returning the whole list of the nuget versions + options = {}; } if (ExtensionConfigurations.nugetPath) { @@ -78,7 +86,21 @@ export class PqSdkNugetPackageService { ); } - if (sortedNugetVersions.length && !sortedNugetVersions[sortedNugetVersions.length - 1].isZero()) { + if (ExtensionConfigurations.externalsVersionTag === "Customized") { + // need to find the closest version among the result list: + const expectedNugetVersion: NugetVersions = NugetVersions.createFromFuzzyVersionString( + ExtensionConfigurations.PQTestVersion || ExtensionConstants.SuggestedPqTestNugetVersion, + ); + + const closestVersion: NugetVersions = NugetVersions.findClosetAmong( + sortedNugetVersions, + expectedNugetVersion, + ); + + return closestVersion === NugetVersions.ZERO_VERSION + ? ExtensionConstants.SuggestedPqTestNugetVersion + : closestVersion.toString(); + } else if (sortedNugetVersions.length && !sortedNugetVersions[sortedNugetVersions.length - 1].isZero()) { return sortedNugetVersions[sortedNugetVersions.length - 1].toString(); } else { return undefined; diff --git a/src/common/nuget/NugetCommandService.ts b/src/common/nuget/NugetCommandService.ts index 24f2bc41..5ae4c5c2 100644 --- a/src/common/nuget/NugetCommandService.ts +++ b/src/common/nuget/NugetCommandService.ts @@ -9,6 +9,7 @@ import * as fs from "fs"; import * as path from "path"; import * as process from "process"; +import { assertNotNull } from "../../utils/assertUtils"; import { ExtensionConstants } from "../../constants/PowerQuerySdkExtension"; import { NugetVersions } from "../../utils/NugetVersions"; import type { PqSdkOutputChannel } from "../../features/PqSdkOutputChannel"; @@ -147,10 +148,11 @@ export class NugetCommandService { ).sort(NugetVersions.compare); if (options.maximumNugetVersion) { + const maximumNugetVersion: NugetVersions = assertNotNull(options.maximumNugetVersion); + // filter out any version gt maximumNugetVersion in sortedNugetVersions sortedNugetVersions = sortedNugetVersions.filter( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - (one: NugetVersions) => one.compare(options.maximumNugetVersion!) <= 0, + (one: NugetVersions) => one.compare(maximumNugetVersion) <= 0, ); } diff --git a/src/common/nuget/NugetLiteHttpService.ts b/src/common/nuget/NugetLiteHttpService.ts index 0f8f4177..6507c08d 100644 --- a/src/common/nuget/NugetLiteHttpService.ts +++ b/src/common/nuget/NugetLiteHttpService.ts @@ -14,6 +14,7 @@ import { promisify } from "util"; import { StreamZipAsync } from "node-stream-zip"; import axios, { AxiosInstance, AxiosResponse } from "axios"; +import { assertNotNull } from "../../utils/assertUtils"; import { makeOneTmpDir } from "../../utils/osUtils"; import { NugetVersions } from "../../utils/NugetVersions"; import { tryRemoveDirectoryRecursively } from "../../utils/files"; @@ -125,10 +126,11 @@ export class NugetLiteHttpService { .sort(NugetVersions.compare); if (options.maximumNugetVersion) { + const maximumNugetVersion: NugetVersions = assertNotNull(options.maximumNugetVersion); + // filter out any version gt maximumNugetVersion in sortedNugetVersions sortedNugetVersions = sortedNugetVersions.filter( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - (one: NugetVersions) => one.compare(options.maximumNugetVersion!) <= 0, + (one: NugetVersions) => one.compare(maximumNugetVersion) <= 0, ); } diff --git a/src/constants/PowerQuerySdkConfiguration.ts b/src/constants/PowerQuerySdkConfiguration.ts index 3b256037..cec009f7 100644 --- a/src/constants/PowerQuerySdkConfiguration.ts +++ b/src/constants/PowerQuerySdkConfiguration.ts @@ -8,7 +8,7 @@ import * as path from "path"; import * as vscode from "vscode"; -import { ExtensionConstants, PqModeType } from "./PowerQuerySdkExtension"; +import { ExtensionConstants, PqModeType, SdkExternalsVersionTags } from "./PowerQuerySdkExtension"; // eslint-disable-next-line @typescript-eslint/typedef export const ExtensionConfigurations = { @@ -145,6 +145,16 @@ export const ExtensionConfigurations = { return config.get(ExtensionConstants.ConfigNames.PowerQuerySdk.properties.externalsNugetFeed); }, + get externalsVersionTag(): SdkExternalsVersionTags { + const config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration( + ExtensionConstants.ConfigNames.PowerQuerySdk.name, + ); + + return config.get( + ExtensionConstants.ConfigNames.PowerQuerySdk.properties.externalsVersionTag, + ) as SdkExternalsVersionTags; + }, + setPQTestLocation( pqTestLocation: string | undefined, configurationTarget: vscode.ConfigurationTarget | boolean | null = vscode.ConfigurationTarget.Global, @@ -173,6 +183,30 @@ export const ExtensionConfigurations = { ); }, + setPQTestVersion( + pqTestVersion: string | undefined, + configurationTarget: vscode.ConfigurationTarget | boolean | null = vscode.ConfigurationTarget.Global, + ): Thenable { + // we should not cache it + const config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration( + ExtensionConstants.ConfigNames.PowerQuerySdk.name, + ); + + return config.update( + ExtensionConstants.ConfigNames.PowerQuerySdk.properties.pqTestVersion, + pqTestVersion, + configurationTarget, + ); + }, + get PQTestVersion(): string | undefined { + // we should not cache it + const config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration( + ExtensionConstants.ConfigNames.PowerQuerySdk.name, + ); + + return config.get(ExtensionConstants.ConfigNames.PowerQuerySdk.properties.pqTestVersion); + }, + setDefaultExtensionLocation( PQTestExtensionFileLocation: string, configurationTarget: vscode.ConfigurationTarget | boolean = vscode.ConfigurationTarget.Workspace, diff --git a/src/constants/PowerQuerySdkExtension.ts b/src/constants/PowerQuerySdkExtension.ts index 75826be5..eac70e47 100644 --- a/src/constants/PowerQuerySdkExtension.ts +++ b/src/constants/PowerQuerySdkExtension.ts @@ -11,6 +11,7 @@ import * as os from "os"; const ExtensionId: string = "vscode-powerquery-sdk"; export type PqModeType = "Power Query" | "SDK"; +export type SdkExternalsVersionTags = "Recommended" | "Latest" | "Customized"; // eslint-disable-next-line @typescript-eslint/typedef const ConfigNames = { @@ -35,7 +36,9 @@ const ConfigNames = { externalsMsbuildPath: "externals.msbuildPath" as const, externalsNugetPath: "externals.nugetPath" as const, externalsNugetFeed: "externals.nugetFeed" as const, + externalsVersionTag: "externals.versionTag" as const, pqTestLocation: "tools.location" as const, + pqTestVersion: "tools.version" as const, defaultExtensionLocation: "defaultExtension" as const, defaultQueryFileLocation: "defaultQueryFile" as const, featureUseServiceHost: "features.useServiceHost" as const, diff --git a/src/utils/NugetVersions.ts b/src/utils/NugetVersions.ts index 50692182..ed1ca780 100644 --- a/src/utils/NugetVersions.ts +++ b/src/utils/NugetVersions.ts @@ -134,6 +134,48 @@ export class NugetVersions { ); } + public static distance(l: NugetVersions, r: NugetVersions): number { + const majorDist: number = NugetVersions.compareIdentifiers(l.major, r.major) * 1e6; + + if (majorDist !== 0) { + return majorDist; + } else { + const minorDist: number = NugetVersions.compareIdentifiers(l.minor, r.minor) * 1e3; + + if (minorDist !== 0) { + return minorDist; + } + + return NugetVersions.compareIdentifiers(l.patch, r.patch); + } + } + + public static findClosetAmong(sortedVersionArr: NugetVersions[], expectedVersion: NugetVersions): NugetVersions { + if (sortedVersionArr.length === 0) { + return NugetVersions.ZERO_VERSION; + } else if (sortedVersionArr.length === 1) { + return sortedVersionArr[0]; + } + + let closestVersion: NugetVersions = sortedVersionArr[0]; + let minDistance: number = NugetVersions.distance(expectedVersion, closestVersion); + + for (let i: number = 1; i < sortedVersionArr.length; i++) { + const distance: number = NugetVersions.distance(expectedVersion, sortedVersionArr[i]); + + if (distance < 0) { + break; + } + + if (distance <= minDistance) { + closestVersion = sortedVersionArr[i]; + minDistance = distance; + } + } + + return closestVersion; + } + private static compareIdentifiers(l: string, r: string): number { const isLNumber: boolean = NumericRegExp.test(l); const isRNumber: boolean = NumericRegExp.test(r); diff --git a/src/utils/assertUtils.ts b/src/utils/assertUtils.ts new file mode 100644 index 00000000..320381a0 --- /dev/null +++ b/src/utils/assertUtils.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the MIT license found in the + * LICENSE file in the root of this projects source tree. + */ + +import * as assert from "assert"; + +export function assertNotNull(value: T | undefined, errorMessage: string = "Found an unexpected nullable value"): T { + assert.ok(Boolean(value), errorMessage); + + return value as T; +} diff --git a/unit-tests/common/nuget/NugetVersions.spec.ts b/unit-tests/common/nuget/NugetVersions.spec.ts index c81fbc1d..88ffc43b 100644 --- a/unit-tests/common/nuget/NugetVersions.spec.ts +++ b/unit-tests/common/nuget/NugetVersions.spec.ts @@ -10,6 +10,31 @@ import { NugetVersions } from "../../../src/utils/NugetVersions"; const expect = chai.expect; +const dummyNugetVersionsArrV1 = [ + NugetVersions.createFromReleasedVersionString("2.2.1"), + NugetVersions.createFromReleasedVersionString("1.1.0"), + NugetVersions.createFromReleasedVersionString("1.3.0"), + NugetVersions.createFromReleasedVersionString("1.2.0"), + NugetVersions.createFromReleasedVersionString("2.0.0"), + NugetVersions.createFromReleasedVersionString("2.1.1"), + NugetVersions.createFromReleasedVersionString("2.1.0"), + NugetVersions.createFromReleasedVersionString("1.2.1"), + NugetVersions.createFromReleasedVersionString("1.0.0"), + NugetVersions.createFromReleasedVersionString("2.2.0"), +].sort(NugetVersions.compare); + +const dummyNugetVersionsArrV2 = [ + NugetVersions.createFromReleasedVersionString("2.116.201"), + NugetVersions.createFromReleasedVersionString("2.114.4"), + NugetVersions.createFromReleasedVersionString("2.112.4"), + NugetVersions.createFromReleasedVersionString("2.111.5"), + NugetVersions.createFromReleasedVersionString("2.111.3"), + NugetVersions.createFromReleasedVersionString("2.110.3"), + NugetVersions.createFromReleasedVersionString("2.110.2"), + NugetVersions.createFromReleasedVersionString("2.110.1"), + NugetVersions.createFromReleasedVersionString("2.109.6"), +].sort(NugetVersions.compare); + describe("NugetVersions.spec unit testes", () => { it("createFromReleasedVersionString v1", () => { const nugetVersions = NugetVersions.createFromReleasedVersionString("2.107.1"); @@ -68,4 +93,98 @@ describe("NugetVersions.spec unit testes", () => { expect(nugetVersionsJunior.compare(nugetVersions)).lt(0); expect(nugetVersionsSenior.compare(nugetVersions)).gt(0); }); + + it("findClosestVersion v1", () => { + let expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("1.2.1"); + let closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV1, expectedNugetVersion); + expect(closestNuGetVersion.major).eq("1"); + expect(closestNuGetVersion.minor).eq("2"); + expect(closestNuGetVersion.patch).eq("1"); + + expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("1.3.3"); + closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV1, expectedNugetVersion); + expect(closestNuGetVersion.major).eq("1"); + expect(closestNuGetVersion.minor).eq("3"); + expect(closestNuGetVersion.patch).eq("0"); + }); + + it("findClosestVersion v2", () => { + let expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.114.4"); + let closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion); + expect(closestNuGetVersion.major).eq("2"); + expect(closestNuGetVersion.minor).eq("114"); + expect(closestNuGetVersion.patch).eq("4"); + + expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2"); + closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion); + expect(closestNuGetVersion.major).eq("2"); + expect(closestNuGetVersion.minor).eq("116"); + expect(closestNuGetVersion.patch).eq("201"); + + expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2."); + closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion); + expect(closestNuGetVersion.major).eq("2"); + expect(closestNuGetVersion.minor).eq("116"); + expect(closestNuGetVersion.patch).eq("201"); + + expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.1"); + closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion); + expect(closestNuGetVersion.major).eq("2"); + expect(closestNuGetVersion.minor).eq("109"); + expect(closestNuGetVersion.patch).eq("6"); + + expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.11"); + closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion); + expect(closestNuGetVersion.major).eq("2"); + expect(closestNuGetVersion.minor).eq("109"); + expect(closestNuGetVersion.patch).eq("6"); + + expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.110"); + closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion); + expect(closestNuGetVersion.major).eq("2"); + expect(closestNuGetVersion.minor).eq("110"); + expect(closestNuGetVersion.patch).eq("3"); + + expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.110."); + closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion); + expect(closestNuGetVersion.major).eq("2"); + expect(closestNuGetVersion.minor).eq("110"); + expect(closestNuGetVersion.patch).eq("3"); + + expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.110.x"); + closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion); + expect(closestNuGetVersion.major).eq("2"); + expect(closestNuGetVersion.minor).eq("110"); + expect(closestNuGetVersion.patch).eq("3"); + + expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.110.4"); + closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion); + expect(closestNuGetVersion.major).eq("2"); + expect(closestNuGetVersion.minor).eq("110"); + expect(closestNuGetVersion.patch).eq("3"); + + expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.115"); + closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion); + expect(closestNuGetVersion.major).eq("2"); + expect(closestNuGetVersion.minor).eq("114"); + expect(closestNuGetVersion.patch).eq("4"); + + expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.115.1000000000000000000"); + closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion); + expect(closestNuGetVersion.major).eq("2"); + expect(closestNuGetVersion.minor).eq("114"); + expect(closestNuGetVersion.patch).eq("4"); + + expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.116"); + closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion); + expect(closestNuGetVersion.major).eq("2"); + expect(closestNuGetVersion.minor).eq("116"); + expect(closestNuGetVersion.patch).eq("201"); + + expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.116.201"); + closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion); + expect(closestNuGetVersion.major).eq("2"); + expect(closestNuGetVersion.minor).eq("116"); + expect(closestNuGetVersion.patch).eq("201"); + }); }); From bb164d3f023189a062b6a48eecef1fcd98080714 Mon Sep 17 00:00:00 2001 From: Albert Li Date: Tue, 28 Mar 2023 00:14:04 +0800 Subject: [PATCH 2/3] feat. support version tag p2: i18n strings --- package.json | 10 +++++----- package.nls.json | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d50676f5..03fec444 100644 --- a/package.json +++ b/package.json @@ -186,11 +186,11 @@ "Customized" ], "enumDescriptions": [ - "The stable version", - "The latest version, working only with explict nuget path", - "Use a user provided version value" + "%extension.pqtest.config.externals.versionTag.recommended.description%", + "%extension.pqtest.config.externals.versionTag.latest.description%", + "%extension.pqtest.config.externals.versionTag.customized.description%" ], - "description": "The version tag of the external tools to be seized.", + "description": "%extension.pqtest.config.externals.versionTag.description%", "default": "Recommended", "order": 50 }, @@ -218,7 +218,7 @@ "scope": "machine-overridable", "type": "string", "order": 71, - "description": "The current version of the pqTest." + "description": "%extension.pqtest.config.pqtest.version.description%" } } }, diff --git a/package.nls.json b/package.nls.json index 87827693..8059a244 100644 --- a/package.nls.json +++ b/package.nls.json @@ -12,9 +12,14 @@ "extension.pqtest.config.externals.msbuildPath.description": "Local path to msbuild.exe installation folder.", "extension.pqtest.config.externals.nugetPath.description": "Local path to nuget.exe installation folder.", "extension.pqtest.config.externals.nugetFeed.description": "Suggested nuget feed URL.", + "extension.pqtest.config.externals.versionTag.description": "The version tag of the PQ Sdk tools to be seized.", + "extension.pqtest.config.externals.versionTag.recommended.description": "The stable version", + "extension.pqtest.config.externals.versionTag.latest.description": "The latest version", + "extension.pqtest.config.externals.versionTag.customized.description": "Use a user provided version value", "extension.pqtest.config.features.autoDetection.description": "When set to false, the SDK does not try to automatically detect connector workspaces and prompt to create a settings file.", "extension.pqtest.config.features.useServiceHost": "Try the new feature using a reusable engine service host other than the command lines", "extension.pqtest.config.pqtest.location.description": "Local path to PQTest installation folder.", + "extension.pqtest.config.pqtest.version.description": "The local PQ SDK tools version.", "extension.pqtest.config.pqtest.extension.description": "Specify connector extension source modules (.mez/.pqm).
This option can be specified more than once.", "extension.pqtest.config.pqtest.queryFile.description": "Query file containing section document or M expression (.m/.pq).", "extension.pqtest.taskDefinitions.properties.operation.description": "The operation to run", From 5e99caca04afeedaaf80249378c9edebe7386393 Mon Sep 17 00:00:00 2001 From: Albert Li Date: Tue, 28 Mar 2023 11:31:19 +0800 Subject: [PATCH 3/3] feat. support version tag p3: lint i18n strings --- package.json | 2 +- package.nls.json | 2 +- src/commands/LifecycleCommands.ts | 4 ++-- src/common/PqSdkNugetPackageService.ts | 2 +- src/constants/PowerQuerySdkExtension.ts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 03fec444..7c7793d5 100644 --- a/package.json +++ b/package.json @@ -183,7 +183,7 @@ "enum": [ "Recommended", "Latest", - "Customized" + "Custom" ], "enumDescriptions": [ "%extension.pqtest.config.externals.versionTag.recommended.description%", diff --git a/package.nls.json b/package.nls.json index 8059a244..2dfda400 100644 --- a/package.nls.json +++ b/package.nls.json @@ -12,7 +12,7 @@ "extension.pqtest.config.externals.msbuildPath.description": "Local path to msbuild.exe installation folder.", "extension.pqtest.config.externals.nugetPath.description": "Local path to nuget.exe installation folder.", "extension.pqtest.config.externals.nugetFeed.description": "Suggested nuget feed URL.", - "extension.pqtest.config.externals.versionTag.description": "The version tag of the PQ Sdk tools to be seized.", + "extension.pqtest.config.externals.versionTag.description": "The version tag of the PQ SDK tools to be downloaded.", "extension.pqtest.config.externals.versionTag.recommended.description": "The stable version", "extension.pqtest.config.externals.versionTag.latest.description": "The latest version", "extension.pqtest.config.externals.versionTag.customized.description": "Use a user provided version value", diff --git a/src/commands/LifecycleCommands.ts b/src/commands/LifecycleCommands.ts index 84ccb74c..a8fb8f26 100644 --- a/src/commands/LifecycleCommands.ts +++ b/src/commands/LifecycleCommands.ts @@ -92,12 +92,12 @@ export class LifecycleCommands implements IDisposable { // when PqTestVersion changed in the mode non-customized version tag, users might have it updated // thus we need to compare it with current expected version and reset it back the one we expected if ( - ExtensionConfigurations.externalsVersionTag !== "Customized" && + ExtensionConfigurations.externalsVersionTag !== "Custom" && this.currentPqTestVersion !== ExtensionConfigurations.PQTestVersion ) { void ExtensionConfigurations.setPQTestVersion(this.currentPqTestVersion); } else if ( - ExtensionConfigurations.externalsVersionTag == "Customized" && + ExtensionConfigurations.externalsVersionTag == "Custom" && this.currentPqTestVersion !== ExtensionConfigurations.PQTestVersion ) { // need to trigger debounced manuallyUpdatePqTest diff --git a/src/common/PqSdkNugetPackageService.ts b/src/common/PqSdkNugetPackageService.ts index 0bbfe2a4..142ed61f 100644 --- a/src/common/PqSdkNugetPackageService.ts +++ b/src/common/PqSdkNugetPackageService.ts @@ -86,7 +86,7 @@ export class PqSdkNugetPackageService { ); } - if (ExtensionConfigurations.externalsVersionTag === "Customized") { + if (ExtensionConfigurations.externalsVersionTag === "Custom") { // need to find the closest version among the result list: const expectedNugetVersion: NugetVersions = NugetVersions.createFromFuzzyVersionString( ExtensionConfigurations.PQTestVersion || ExtensionConstants.SuggestedPqTestNugetVersion, diff --git a/src/constants/PowerQuerySdkExtension.ts b/src/constants/PowerQuerySdkExtension.ts index eac70e47..de8c4190 100644 --- a/src/constants/PowerQuerySdkExtension.ts +++ b/src/constants/PowerQuerySdkExtension.ts @@ -11,7 +11,7 @@ import * as os from "os"; const ExtensionId: string = "vscode-powerquery-sdk"; export type PqModeType = "Power Query" | "SDK"; -export type SdkExternalsVersionTags = "Recommended" | "Latest" | "Customized"; +export type SdkExternalsVersionTags = "Recommended" | "Latest" | "Custom"; // eslint-disable-next-line @typescript-eslint/typedef const ConfigNames = {