From c729bfeb832f269b5650dd4c7ffadc35f2dd6527 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 27 Apr 2021 19:06:01 -0700 Subject: [PATCH 01/24] Export extension version memento --- src/client/common/startPage/startPage.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/client/common/startPage/startPage.ts b/src/client/common/startPage/startPage.ts index dae649bfc59b..50d0a7ad2d01 100644 --- a/src/client/common/startPage/startPage.ts +++ b/src/client/common/startPage/startPage.ts @@ -28,6 +28,8 @@ import { WebviewPanelHost } from './webviewPanelHost'; const startPageDir = path.join(EXTENSION_ROOT_DIR, 'out', 'startPage-ui', 'viewers'); +export const EXTENSION_VERSION_MEMENTO = 'extensionVersion'; + // Class that opens, disposes and handles messages and actions for the Python Extension Start Page. // It also runs when the extension activates. @injectable() @@ -129,7 +131,7 @@ export class StartPage extends WebviewPanelHost sendTelemetryEvent(Telemetry.StartPageOpenBlankNotebook); this.setTelemetryFlags(); - const savedVersion: string | undefined = this.context.globalState.get('extensionVersion'); + const savedVersion: string | undefined = this.context.globalState.get(EXTENSION_VERSION_MEMENTO); if (savedVersion) { await this.commandManager.executeCommand( @@ -220,7 +222,7 @@ export class StartPage extends WebviewPanelHost // Public for testing public async extensionVersionChanged(): Promise { - const savedVersion: string | undefined = this.context.globalState.get('extensionVersion'); + const savedVersion: string | undefined = this.context.globalState.get(EXTENSION_VERSION_MEMENTO); const version: string = this.appEnvironment.packageJson.version; let shouldShowStartPage: boolean; @@ -239,7 +241,7 @@ export class StartPage extends WebviewPanelHost // savedVersion being undefined means this is the first time the user activates the extension. // if savedVersion != version, there was an update - await this.context.globalState.update('extensionVersion', version); + await this.context.globalState.update(EXTENSION_VERSION_MEMENTO, version); return shouldShowStartPage; } From 8b31ad465b14b60c00f19fac71802277cff4f4ea Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 27 Apr 2021 19:06:10 -0700 Subject: [PATCH 02/24] Add localization strings --- src/client/common/utils/localize.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 05458ccc3db7..d700e57f0bcf 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -130,6 +130,11 @@ export namespace Pylance { ); export const pylanceInstallPylance = localize('Pylance.pylanceInstallPylance', 'Install Pylance'); export const pylanceRevertToJedi = localize('Pylance.pylanceRevertToJedi', 'Revert to Jedi'); + + export const pylanceDefaultLSMessage = localize( + 'Pylance.pylanceDefaultLSMessage', + 'The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here].\n\nRead Pylance’s license [here]', + ); } export namespace Jupyter { From 26fc28c4492ac28855179e4f21a3729c4a0a4010 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 27 Apr 2021 19:06:22 -0700 Subject: [PATCH 03/24] Add prompt check --- .../defaultLanguageServerActivation.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/client/activation/defaultLanguageServerActivation.ts diff --git a/src/client/activation/defaultLanguageServerActivation.ts b/src/client/activation/defaultLanguageServerActivation.ts new file mode 100644 index 000000000000..0a5c3a4550c8 --- /dev/null +++ b/src/client/activation/defaultLanguageServerActivation.ts @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject } from 'inversify'; +import { IApplicationShell } from '../common/application/types'; +import { EXTENSION_VERSION_MEMENTO } from '../common/startPage/startPage'; +import { IExtensionContext } from '../common/types'; +import { Common, Pylance } from '../common/utils/localize'; +import { IExtensionSingleActivationService } from './types'; + +export const PYLANCE_PROMPT_MEMENTO = 'pylanceDefaultPromptMemento'; + +export class DefaultLanguageServerActivation implements IExtensionSingleActivationService { + constructor( + @inject(IApplicationShell) private appShell: IApplicationShell, + @inject(IExtensionContext) private readonly context: IExtensionContext, + ) {} + + public async activate(): Promise { + if (this.shouldShowPrompt()) { + await this.showPrompt(); + } + } + + private shouldShowPrompt(): boolean { + const savedVersion: string | undefined = this.context.globalState.get(EXTENSION_VERSION_MEMENTO); + const promptShown: boolean | undefined = this.context.globalState.get(PYLANCE_PROMPT_MEMENTO); + + // savedVersion being undefined means that this is the first time the user activates the extension. + // promptShown being undefined means that this is the first time we check if we should show the prompt. + return savedVersion !== undefined && promptShown === undefined; + } + + private async showPrompt(): Promise { + await this.appShell.showInformationMessage(Pylance.pylanceDefaultLSMessage(), Common.ok()).then(async () => { + await this.context.globalState.update(PYLANCE_PROMPT_MEMENTO, true); + }); + } +} From 3b2218406b9a720cf784494b45a335f388a05327 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 27 Apr 2021 19:07:01 -0700 Subject: [PATCH 04/24] Add to the list of things triggering on activation --- src/client/extensionActivation.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index d6da4616520d..5da7dae353eb 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -62,6 +62,7 @@ import * as pythonEnvironments from './pythonEnvironments'; import { ActivationResult, ExtensionState } from './components'; import { Components } from './extensionInit'; import { setDefaultLanguageServerByExperiment } from './common/experiments/helpers'; +import { DefaultLanguageServerActivation } from './activation/defaultLanguageServerActivation'; export async function activateComponents( // `ext` is passed to any extra activation funcs. @@ -126,6 +127,11 @@ async function activateLegacy(ext: ExtensionState): Promise { debugConfigurationRegisterTypes(serviceManager); tensorBoardRegisterTypes(serviceManager); + serviceManager.addSingleton( + IExtensionSingleActivationService, + DefaultLanguageServerActivation, + ); + const experimentService = serviceContainer.get(IExperimentService); // This guarantees that all experiment information has loaded & all telemetry will contain experiment info. await experimentService.activate(); From df73faec6f0fea80aad08889aad344aaaf70c7c4 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 27 Apr 2021 19:15:56 -0700 Subject: [PATCH 05/24] Add tests --- ...faultLanguageServerActivation.unit.test.ts | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 src/test/activation/defaultLanguageServerActivation.unit.test.ts diff --git a/src/test/activation/defaultLanguageServerActivation.unit.test.ts b/src/test/activation/defaultLanguageServerActivation.unit.test.ts new file mode 100644 index 000000000000..d5dcb27de3a5 --- /dev/null +++ b/src/test/activation/defaultLanguageServerActivation.unit.test.ts @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as assert from 'assert'; +import * as typemoq from 'typemoq'; +import { ExtensionContext } from 'vscode'; +import { + DefaultLanguageServerActivation, + PYLANCE_PROMPT_MEMENTO, +} from '../../client/activation/defaultLanguageServerActivation'; +import { IApplicationShell } from '../../client/common/application/types'; +import { EXTENSION_VERSION_MEMENTO } from '../../client/common/startPage/startPage'; +import { IExtensionContext } from '../../client/common/types'; +import { Pylance } from '../../client/common/utils/localize'; + +suite('Default language server - Show prompt', () => { + let appShell: typemoq.IMock; + let context: typemoq.IMock; + let memento: typemoq.IMock; + let updatedPromptMemento: { key: string; value: unknown } | undefined; + + setup(() => { + appShell = typemoq.Mock.ofType(); + context = typemoq.Mock.ofType(); + memento = typemoq.Mock.ofType(); + + context.setup((c) => c.globalState).returns(() => memento.object); + memento + .setup((m) => m.update(PYLANCE_PROMPT_MEMENTO, typemoq.It.isAny())) + .returns((key: string, value: unknown) => { + updatedPromptMemento = { key, value }; + + return Promise.resolve() as Thenable; + }); + }); + + function setupMementos(version?: string, promptShown?: boolean) { + memento.setup((m) => m.get(EXTENSION_VERSION_MEMENTO)).returns(() => version); + memento.setup((m) => m.get(PYLANCE_PROMPT_MEMENTO)).returns(() => promptShown); + } + + teardown(() => { + context.reset(); + memento.reset(); + updatedPromptMemento = undefined; + }); + + test("Should show prompt if it's an existing installation of the extension and the prompt has not been shown yet", async () => { + setupMementos('1.0.0', undefined); + + appShell + .setup((a) => + a.showInformationMessage( + typemoq.It.isValue(Pylance.pylanceDefaultLSMessage()), + typemoq.It.isAnyString(), + ), + ) + .returns(() => Promise.resolve(undefined)) + .verifiable(typemoq.Times.once()); + + const defaultLanguageServerActivation = new DefaultLanguageServerActivation(appShell.object, context.object); + await defaultLanguageServerActivation.activate(); + + appShell.verifyAll(); + assert.strictEqual(updatedPromptMemento?.key, PYLANCE_PROMPT_MEMENTO); + assert.strictEqual(updatedPromptMemento?.value, true); + }); + + test('Should not show prompt if it has been shown before', async () => { + setupMementos('1.0.0', true); + + appShell + .setup((a) => + a.showInformationMessage( + typemoq.It.isValue(Pylance.pylanceDefaultLSMessage()), + typemoq.It.isAnyString(), + ), + ) + .returns(() => Promise.resolve(undefined)) + .verifiable(typemoq.Times.never()); + + const defaultLanguageServerActivation = new DefaultLanguageServerActivation(appShell.object, context.object); + await defaultLanguageServerActivation.activate(); + + appShell.verifyAll(); + assert.strictEqual(updatedPromptMemento, undefined); + }); + + test('Should not show prompt if it is a new installation of the extension', async () => { + setupMementos(undefined, undefined); + + appShell + .setup((a) => + a.showInformationMessage( + typemoq.It.isValue(Pylance.pylanceDefaultLSMessage()), + typemoq.It.isAnyString(), + ), + ) + .returns(() => Promise.resolve(undefined)) + .verifiable(typemoq.Times.never()); + + const defaultLanguageServerActivation = new DefaultLanguageServerActivation(appShell.object, context.object); + await defaultLanguageServerActivation.activate(); + + appShell.verifyAll(); + assert.strictEqual(updatedPromptMemento, undefined); + }); +}); From 95f98a7128180369bb64b53be2d1b26ed774b201 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 28 Apr 2021 08:44:04 -0700 Subject: [PATCH 06/24] Rename file --- ...erActivation.ts => defaultLanguageServerSupport.ts} | 5 +++-- src/client/extensionActivation.ts | 4 ++-- ...st.ts => defaultLanguageServerSupport.unit.test.ts} | 10 +++++----- 3 files changed, 10 insertions(+), 9 deletions(-) rename src/client/activation/{defaultLanguageServerActivation.ts => defaultLanguageServerSupport.ts} (91%) rename src/test/activation/{defaultLanguageServerActivation.unit.test.ts => defaultLanguageServerSupport.unit.test.ts} (93%) diff --git a/src/client/activation/defaultLanguageServerActivation.ts b/src/client/activation/defaultLanguageServerSupport.ts similarity index 91% rename from src/client/activation/defaultLanguageServerActivation.ts rename to src/client/activation/defaultLanguageServerSupport.ts index 0a5c3a4550c8..f0ff60ac2ced 100644 --- a/src/client/activation/defaultLanguageServerActivation.ts +++ b/src/client/activation/defaultLanguageServerSupport.ts @@ -3,7 +3,7 @@ 'use strict'; -import { inject } from 'inversify'; +import { inject, injectable } from 'inversify'; import { IApplicationShell } from '../common/application/types'; import { EXTENSION_VERSION_MEMENTO } from '../common/startPage/startPage'; import { IExtensionContext } from '../common/types'; @@ -12,7 +12,8 @@ import { IExtensionSingleActivationService } from './types'; export const PYLANCE_PROMPT_MEMENTO = 'pylanceDefaultPromptMemento'; -export class DefaultLanguageServerActivation implements IExtensionSingleActivationService { +@injectable() +export class DefaultLanguageServerSupport implements IExtensionSingleActivationService { constructor( @inject(IApplicationShell) private appShell: IApplicationShell, @inject(IExtensionContext) private readonly context: IExtensionContext, diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 5da7dae353eb..6b2ae7754ff5 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -62,7 +62,7 @@ import * as pythonEnvironments from './pythonEnvironments'; import { ActivationResult, ExtensionState } from './components'; import { Components } from './extensionInit'; import { setDefaultLanguageServerByExperiment } from './common/experiments/helpers'; -import { DefaultLanguageServerActivation } from './activation/defaultLanguageServerActivation'; +import { DefaultLanguageServerSupport } from './activation/defaultLanguageServerSupport'; export async function activateComponents( // `ext` is passed to any extra activation funcs. @@ -129,7 +129,7 @@ async function activateLegacy(ext: ExtensionState): Promise { serviceManager.addSingleton( IExtensionSingleActivationService, - DefaultLanguageServerActivation, + DefaultLanguageServerSupport, ); const experimentService = serviceContainer.get(IExperimentService); diff --git a/src/test/activation/defaultLanguageServerActivation.unit.test.ts b/src/test/activation/defaultLanguageServerSupport.unit.test.ts similarity index 93% rename from src/test/activation/defaultLanguageServerActivation.unit.test.ts rename to src/test/activation/defaultLanguageServerSupport.unit.test.ts index d5dcb27de3a5..5604a10ebb21 100644 --- a/src/test/activation/defaultLanguageServerActivation.unit.test.ts +++ b/src/test/activation/defaultLanguageServerSupport.unit.test.ts @@ -7,9 +7,9 @@ import * as assert from 'assert'; import * as typemoq from 'typemoq'; import { ExtensionContext } from 'vscode'; import { - DefaultLanguageServerActivation, + DefaultLanguageServerSupport, PYLANCE_PROMPT_MEMENTO, -} from '../../client/activation/defaultLanguageServerActivation'; +} from '../../client/activation/defaultLanguageServerSupport'; import { IApplicationShell } from '../../client/common/application/types'; import { EXTENSION_VERSION_MEMENTO } from '../../client/common/startPage/startPage'; import { IExtensionContext } from '../../client/common/types'; @@ -60,7 +60,7 @@ suite('Default language server - Show prompt', () => { .returns(() => Promise.resolve(undefined)) .verifiable(typemoq.Times.once()); - const defaultLanguageServerActivation = new DefaultLanguageServerActivation(appShell.object, context.object); + const defaultLanguageServerActivation = new DefaultLanguageServerSupport(appShell.object, context.object); await defaultLanguageServerActivation.activate(); appShell.verifyAll(); @@ -81,7 +81,7 @@ suite('Default language server - Show prompt', () => { .returns(() => Promise.resolve(undefined)) .verifiable(typemoq.Times.never()); - const defaultLanguageServerActivation = new DefaultLanguageServerActivation(appShell.object, context.object); + const defaultLanguageServerActivation = new DefaultLanguageServerSupport(appShell.object, context.object); await defaultLanguageServerActivation.activate(); appShell.verifyAll(); @@ -101,7 +101,7 @@ suite('Default language server - Show prompt', () => { .returns(() => Promise.resolve(undefined)) .verifiable(typemoq.Times.never()); - const defaultLanguageServerActivation = new DefaultLanguageServerActivation(appShell.object, context.object); + const defaultLanguageServerActivation = new DefaultLanguageServerSupport(appShell.object, context.object); await defaultLanguageServerActivation.activate(); appShell.verifyAll(); From 1c7c2d81e02a2ca2b71f56f5637641a6c58aa131 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 28 Apr 2021 08:44:16 -0700 Subject: [PATCH 07/24] Remove unsupported newlines --- src/client/common/utils/localize.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index d700e57f0bcf..ddd03f897c65 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -133,7 +133,7 @@ export namespace Pylance { export const pylanceDefaultLSMessage = localize( 'Pylance.pylanceDefaultLSMessage', - 'The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here].\n\nRead Pylance’s license [here]', + 'The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here]. Read Pylance’s license [here]', ); } From 23002d8d5bab31fa19f77868d073ca88c0fcc553 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 28 Apr 2021 08:51:02 -0700 Subject: [PATCH 08/24] Fix localization + re-add newlines --- package.nls.json | 1 + src/client/common/utils/localize.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.nls.json b/package.nls.json index a88ba79a1fda..79a12b6ed920 100644 --- a/package.nls.json +++ b/package.nls.json @@ -56,6 +56,7 @@ "Pylance.pylanceRevertToJediPrompt": "The Pylance extension is not installed but the python.languageServer value is set to \"Pylance\". Would you like to install the Pylance extension to use Pylance, or revert back to Jedi?", "Pylance.pylanceInstallPylance": "Install Pylance", "Pylance.pylanceRevertToJedi": "Revert to Jedi", + "Pylance.pylanceDefaultLSMessage": "The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here].\n\nRead Pylance’s license [here]", "Experiments.inGroup": "User belongs to experiment group '{0}'", "Experiments.optedOutOf": "User opted out of experiment group '{0}'", "Interpreters.RefreshingInterpreters": "Refreshing Python Interpreters", diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index ddd03f897c65..d700e57f0bcf 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -133,7 +133,7 @@ export namespace Pylance { export const pylanceDefaultLSMessage = localize( 'Pylance.pylanceDefaultLSMessage', - 'The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here]. Read Pylance’s license [here]', + 'The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here].\n\nRead Pylance’s license [here]', ); } From 8d4c741c16349a1417a01ff97415335873608c47 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 28 Apr 2021 17:01:53 -0700 Subject: [PATCH 09/24] Change to be a non-blocking diagnostic check --- .../defaultLanguageServerSupport.ts | 42 ------ .../diagnostics/checks/pylanceDefault.ts | 84 +++++++++++ .../application/diagnostics/constants.ts | 1 + .../diagnostics/serviceRegistry.ts | 8 + src/client/common/utils/localize.ts | 9 +- src/client/extensionActivation.ts | 6 - .../defaultLanguageServerSupport.unit.test.ts | 110 -------------- .../checks/pylanceDefault.unit.test.ts | 138 ++++++++++++++++++ 8 files changed, 235 insertions(+), 163 deletions(-) delete mode 100644 src/client/activation/defaultLanguageServerSupport.ts create mode 100644 src/client/application/diagnostics/checks/pylanceDefault.ts delete mode 100644 src/test/activation/defaultLanguageServerSupport.unit.test.ts create mode 100644 src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts diff --git a/src/client/activation/defaultLanguageServerSupport.ts b/src/client/activation/defaultLanguageServerSupport.ts deleted file mode 100644 index f0ff60ac2ced..000000000000 --- a/src/client/activation/defaultLanguageServerSupport.ts +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IApplicationShell } from '../common/application/types'; -import { EXTENSION_VERSION_MEMENTO } from '../common/startPage/startPage'; -import { IExtensionContext } from '../common/types'; -import { Common, Pylance } from '../common/utils/localize'; -import { IExtensionSingleActivationService } from './types'; - -export const PYLANCE_PROMPT_MEMENTO = 'pylanceDefaultPromptMemento'; - -@injectable() -export class DefaultLanguageServerSupport implements IExtensionSingleActivationService { - constructor( - @inject(IApplicationShell) private appShell: IApplicationShell, - @inject(IExtensionContext) private readonly context: IExtensionContext, - ) {} - - public async activate(): Promise { - if (this.shouldShowPrompt()) { - await this.showPrompt(); - } - } - - private shouldShowPrompt(): boolean { - const savedVersion: string | undefined = this.context.globalState.get(EXTENSION_VERSION_MEMENTO); - const promptShown: boolean | undefined = this.context.globalState.get(PYLANCE_PROMPT_MEMENTO); - - // savedVersion being undefined means that this is the first time the user activates the extension. - // promptShown being undefined means that this is the first time we check if we should show the prompt. - return savedVersion !== undefined && promptShown === undefined; - } - - private async showPrompt(): Promise { - await this.appShell.showInformationMessage(Pylance.pylanceDefaultLSMessage(), Common.ok()).then(async () => { - await this.context.globalState.update(PYLANCE_PROMPT_MEMENTO, true); - }); - } -} diff --git a/src/client/application/diagnostics/checks/pylanceDefault.ts b/src/client/application/diagnostics/checks/pylanceDefault.ts new file mode 100644 index 000000000000..a8bf456e63e4 --- /dev/null +++ b/src/client/application/diagnostics/checks/pylanceDefault.ts @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// eslint-disable-next-line max-classes-per-file +import { inject, named } from 'inversify'; +import { DiagnosticSeverity } from 'vscode'; +import { EXTENSION_VERSION_MEMENTO } from '../../../common/startPage/startPage'; +import { IDisposableRegistry, IExtensionContext, Resource } from '../../../common/types'; +import { Diagnostics, Common } from '../../../common/utils/localize'; +import { IServiceContainer } from '../../../ioc/types'; +import { BaseDiagnostic, BaseDiagnosticsService } from '../base'; +import { DiagnosticCodes } from '../constants'; +import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; +import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; + +export const PYLANCE_PROMPT_MEMENTO = 'pylanceDefaultPromptMemento'; + +export class PylanceDefaultDiagnostic extends BaseDiagnostic { + constructor(message: string, resource: Resource) { + super( + DiagnosticCodes.PylanceDefaultDiagnostic, + message, + DiagnosticSeverity.Information, + DiagnosticScope.Global, + resource, + ); + } +} + +export const PylanceDefaultDiagnosticServiceId = 'PylanceDefaultDiagnosticServiceId'; + +export class PylanceDefaultDiagnosticService extends BaseDiagnosticsService { + constructor( + @inject(IServiceContainer) serviceContainer: IServiceContainer, + @inject(IExtensionContext) private readonly context: IExtensionContext, + @inject(IDiagnosticHandlerService) + @named(DiagnosticCommandPromptHandlerServiceId) + protected readonly messageService: IDiagnosticHandlerService, + @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, + ) { + super([DiagnosticCodes.PylanceDefaultDiagnostic], serviceContainer, disposableRegistry, false); + } + + public async diagnose(resource: Resource): Promise { + if (!this.shouldShowPrompt()) { + return []; + } + return [new PylanceDefaultDiagnostic(Diagnostics.pylanceDefaultLSMessage(), resource)]; + } + + protected async onHandle(diagnostics: IDiagnostic[]): Promise { + if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) { + return; + } + + const diagnostic = diagnostics[0]; + if (await this.filterService.shouldIgnoreDiagnostic(diagnostic.code)) { + return; + } + + const options = [ + { + prompt: Common.ok(), + command: { + diagnostic, + invoke: async (): Promise => { + await this.context.globalState.update(PYLANCE_PROMPT_MEMENTO, true); + }, + }, + }, + ]; + + await this.messageService.handle(diagnostic, { commandPrompts: options }); + } + + private shouldShowPrompt(): boolean { + const savedVersion: string | undefined = this.context.globalState.get(EXTENSION_VERSION_MEMENTO); + const promptShown: boolean | undefined = this.context.globalState.get(PYLANCE_PROMPT_MEMENTO); + + // savedVersion being undefined means that this is the first time the user activates the extension. + // promptShown being undefined means that this is the first time we check if we should show the prompt. + return savedVersion !== undefined && promptShown === undefined; + } +} diff --git a/src/client/application/diagnostics/constants.ts b/src/client/application/diagnostics/constants.ts index a5c8c69a53d3..f03564ff2c42 100644 --- a/src/client/application/diagnostics/constants.ts +++ b/src/client/application/diagnostics/constants.ts @@ -19,4 +19,5 @@ export enum DiagnosticCodes { ConsoleTypeDiagnostic = 'ConsoleTypeDiagnostic', ConfigPythonPathDiagnostic = 'ConfigPythonPathDiagnostic', UpgradeCodeRunnerDiagnostic = 'UpgradeCodeRunnerDiagnostic', + PylanceDefaultDiagnostic = 'PylanceDefaultDiagnostic', } diff --git a/src/client/application/diagnostics/serviceRegistry.ts b/src/client/application/diagnostics/serviceRegistry.ts index 1388a2bab248..1a513ff9255a 100644 --- a/src/client/application/diagnostics/serviceRegistry.ts +++ b/src/client/application/diagnostics/serviceRegistry.ts @@ -28,6 +28,7 @@ import { PowerShellActivationHackDiagnosticsService, PowerShellActivationHackDiagnosticsServiceId, } from './checks/powerShellActivation'; +import { PylanceDefaultDiagnosticService, PylanceDefaultDiagnosticServiceId } from './checks/pylanceDefault'; import { InvalidPythonInterpreterService, InvalidPythonInterpreterServiceId } from './checks/pythonInterpreter'; import { PythonPathDeprecatedDiagnosticService, @@ -92,6 +93,13 @@ export function registerTypes(serviceManager: IServiceManager, languageServerTyp UpgradeCodeRunnerDiagnosticService, UpgradeCodeRunnerDiagnosticServiceId, ); + + serviceManager.addSingleton( + IDiagnosticsService, + PylanceDefaultDiagnosticService, + PylanceDefaultDiagnosticServiceId, + ); + serviceManager.addSingleton(IDiagnosticsCommandFactory, DiagnosticsCommandFactory); serviceManager.addSingleton(IApplicationDiagnostics, ApplicationDiagnostics); diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index d700e57f0bcf..c7a678cd2fc0 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -67,6 +67,10 @@ export namespace Diagnostics { 'diagnostics.checkIsort5UpgradeGuide', 'We found outdated configuration for sorting imports in this workspace. Check the [isort upgrade guide](https://aka.ms/AA9j5x4) to update your settings.', ); + export const pylanceDefaultLSMessage = localize( + 'Pylance.pylanceDefaultLSMessage', + 'The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here].\n\nRead Pylance’s license [here]', + ); } export namespace Common { @@ -130,11 +134,6 @@ export namespace Pylance { ); export const pylanceInstallPylance = localize('Pylance.pylanceInstallPylance', 'Install Pylance'); export const pylanceRevertToJedi = localize('Pylance.pylanceRevertToJedi', 'Revert to Jedi'); - - export const pylanceDefaultLSMessage = localize( - 'Pylance.pylanceDefaultLSMessage', - 'The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here].\n\nRead Pylance’s license [here]', - ); } export namespace Jupyter { diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 6b2ae7754ff5..d6da4616520d 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -62,7 +62,6 @@ import * as pythonEnvironments from './pythonEnvironments'; import { ActivationResult, ExtensionState } from './components'; import { Components } from './extensionInit'; import { setDefaultLanguageServerByExperiment } from './common/experiments/helpers'; -import { DefaultLanguageServerSupport } from './activation/defaultLanguageServerSupport'; export async function activateComponents( // `ext` is passed to any extra activation funcs. @@ -127,11 +126,6 @@ async function activateLegacy(ext: ExtensionState): Promise { debugConfigurationRegisterTypes(serviceManager); tensorBoardRegisterTypes(serviceManager); - serviceManager.addSingleton( - IExtensionSingleActivationService, - DefaultLanguageServerSupport, - ); - const experimentService = serviceContainer.get(IExperimentService); // This guarantees that all experiment information has loaded & all telemetry will contain experiment info. await experimentService.activate(); diff --git a/src/test/activation/defaultLanguageServerSupport.unit.test.ts b/src/test/activation/defaultLanguageServerSupport.unit.test.ts deleted file mode 100644 index 5604a10ebb21..000000000000 --- a/src/test/activation/defaultLanguageServerSupport.unit.test.ts +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as assert from 'assert'; -import * as typemoq from 'typemoq'; -import { ExtensionContext } from 'vscode'; -import { - DefaultLanguageServerSupport, - PYLANCE_PROMPT_MEMENTO, -} from '../../client/activation/defaultLanguageServerSupport'; -import { IApplicationShell } from '../../client/common/application/types'; -import { EXTENSION_VERSION_MEMENTO } from '../../client/common/startPage/startPage'; -import { IExtensionContext } from '../../client/common/types'; -import { Pylance } from '../../client/common/utils/localize'; - -suite('Default language server - Show prompt', () => { - let appShell: typemoq.IMock; - let context: typemoq.IMock; - let memento: typemoq.IMock; - let updatedPromptMemento: { key: string; value: unknown } | undefined; - - setup(() => { - appShell = typemoq.Mock.ofType(); - context = typemoq.Mock.ofType(); - memento = typemoq.Mock.ofType(); - - context.setup((c) => c.globalState).returns(() => memento.object); - memento - .setup((m) => m.update(PYLANCE_PROMPT_MEMENTO, typemoq.It.isAny())) - .returns((key: string, value: unknown) => { - updatedPromptMemento = { key, value }; - - return Promise.resolve() as Thenable; - }); - }); - - function setupMementos(version?: string, promptShown?: boolean) { - memento.setup((m) => m.get(EXTENSION_VERSION_MEMENTO)).returns(() => version); - memento.setup((m) => m.get(PYLANCE_PROMPT_MEMENTO)).returns(() => promptShown); - } - - teardown(() => { - context.reset(); - memento.reset(); - updatedPromptMemento = undefined; - }); - - test("Should show prompt if it's an existing installation of the extension and the prompt has not been shown yet", async () => { - setupMementos('1.0.0', undefined); - - appShell - .setup((a) => - a.showInformationMessage( - typemoq.It.isValue(Pylance.pylanceDefaultLSMessage()), - typemoq.It.isAnyString(), - ), - ) - .returns(() => Promise.resolve(undefined)) - .verifiable(typemoq.Times.once()); - - const defaultLanguageServerActivation = new DefaultLanguageServerSupport(appShell.object, context.object); - await defaultLanguageServerActivation.activate(); - - appShell.verifyAll(); - assert.strictEqual(updatedPromptMemento?.key, PYLANCE_PROMPT_MEMENTO); - assert.strictEqual(updatedPromptMemento?.value, true); - }); - - test('Should not show prompt if it has been shown before', async () => { - setupMementos('1.0.0', true); - - appShell - .setup((a) => - a.showInformationMessage( - typemoq.It.isValue(Pylance.pylanceDefaultLSMessage()), - typemoq.It.isAnyString(), - ), - ) - .returns(() => Promise.resolve(undefined)) - .verifiable(typemoq.Times.never()); - - const defaultLanguageServerActivation = new DefaultLanguageServerSupport(appShell.object, context.object); - await defaultLanguageServerActivation.activate(); - - appShell.verifyAll(); - assert.strictEqual(updatedPromptMemento, undefined); - }); - - test('Should not show prompt if it is a new installation of the extension', async () => { - setupMementos(undefined, undefined); - - appShell - .setup((a) => - a.showInformationMessage( - typemoq.It.isValue(Pylance.pylanceDefaultLSMessage()), - typemoq.It.isAnyString(), - ), - ) - .returns(() => Promise.resolve(undefined)) - .verifiable(typemoq.Times.never()); - - const defaultLanguageServerActivation = new DefaultLanguageServerSupport(appShell.object, context.object); - await defaultLanguageServerActivation.activate(); - - appShell.verifyAll(); - assert.strictEqual(updatedPromptMemento, undefined); - }); -}); diff --git a/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts b/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts new file mode 100644 index 000000000000..e955c9acbe3f --- /dev/null +++ b/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as assert from 'assert'; +import { expect } from 'chai'; +import * as typemoq from 'typemoq'; +import { ExtensionContext } from 'vscode'; +import { BaseDiagnosticsService } from '../../../../client/application/diagnostics/base'; +import { + PylanceDefaultDiagnostic, + PylanceDefaultDiagnosticService, + PYLANCE_PROMPT_MEMENTO, +} from '../../../../client/application/diagnostics/checks/pylanceDefault'; +import { DiagnosticCodes } from '../../../../client/application/diagnostics/constants'; +import { MessageCommandPrompt } from '../../../../client/application/diagnostics/promptHandler'; +import { + IDiagnostic, + IDiagnosticFilterService, + IDiagnosticHandlerService, + IDiagnosticsService, +} from '../../../../client/application/diagnostics/types'; +import { EXTENSION_VERSION_MEMENTO } from '../../../../client/common/startPage/startPage'; +import { IExtensionContext } from '../../../../client/common/types'; +import { Diagnostics } from '../../../../client/common/utils/localize'; +import { IServiceContainer } from '../../../../client/ioc/types'; + +suite('Application Diagnostics - Pylance informational prompt', () => { + let serviceContainer: typemoq.IMock; + let diagnosticService: IDiagnosticsService; + let filterService: typemoq.IMock; + let messageHandler: typemoq.IMock>; + let context: typemoq.IMock; + let memento: typemoq.IMock; + + setup(() => { + serviceContainer = typemoq.Mock.ofType(); + filterService = typemoq.Mock.ofType(); + messageHandler = typemoq.Mock.ofType>(); + context = typemoq.Mock.ofType(); + memento = typemoq.Mock.ofType(); + + serviceContainer + .setup((s) => s.get(typemoq.It.isValue(IDiagnosticFilterService))) + .returns(() => filterService.object); + context.setup((c) => c.globalState).returns(() => memento.object); + + diagnosticService = new (class extends PylanceDefaultDiagnosticService { + // eslint-disable-next-line class-methods-use-this + public _clear() { + while (BaseDiagnosticsService.handledDiagnosticCodeKeys.length > 0) { + BaseDiagnosticsService.handledDiagnosticCodeKeys.shift(); + } + } + })(serviceContainer.object, context.object, messageHandler.object, []); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (diagnosticService as any)._clear(); + }); + + teardown(() => { + context.reset(); + memento.reset(); + }); + + function setupMementos(version?: string, promptShown?: boolean) { + memento.setup((m) => m.get(EXTENSION_VERSION_MEMENTO)).returns(() => version); + memento.setup((m) => m.get(PYLANCE_PROMPT_MEMENTO)).returns(() => promptShown); + } + + test("Should display message if it's an existing installation of the extension and the prompt has not been shown yet", async () => { + setupMementos('1.0.0', undefined); + + const diagnostics = await diagnosticService.diagnose(undefined); + + assert.deepStrictEqual(diagnostics, [ + new PylanceDefaultDiagnostic(Diagnostics.pylanceDefaultLSMessage(), undefined), + ]); + }); + + test("Should return empty diagnostics if it's an existing installation of the extension and the prompt has been shown before", async () => { + setupMementos('1.0.0', true); + + const diagnostics = await diagnosticService.diagnose(undefined); + + assert.deepStrictEqual(diagnostics, []); + }); + + test("Should return empty diagnostics if it's a fresh installation of the extension", async () => { + setupMementos(undefined, undefined); + + const diagnostics = await diagnosticService.diagnose(undefined); + + assert.deepStrictEqual(diagnostics, []); + }); + + test('Should return empty diagnostics if the diagnostic code has been ignored', async () => { + const diagnostic = new PylanceDefaultDiagnostic(DiagnosticCodes.PylanceDefaultDiagnostic, undefined); + + filterService + .setup((f) => f.shouldIgnoreDiagnostic(typemoq.It.isValue(DiagnosticCodes.PylanceDefaultDiagnostic))) + .returns(() => Promise.resolve(true)) + .verifiable(typemoq.Times.once()); + + messageHandler.setup((f) => f.handle(typemoq.It.isAny(), typemoq.It.isAny())).verifiable(typemoq.Times.never()); + + await diagnosticService.handle([diagnostic]); + + filterService.verifyAll(); + messageHandler.verifyAll(); + }); + + test('PylanceDefaultDiagnosticService can handle PylanceDefaultDiagnostic diagnostics', async () => { + const diagnostic = typemoq.Mock.ofType(); + diagnostic + .setup((d) => d.code) + .returns(() => DiagnosticCodes.PylanceDefaultDiagnostic) + .verifiable(typemoq.Times.atLeastOnce()); + + const canHandle = await diagnosticService.canHandle(diagnostic.object); + + expect(canHandle).to.be.equal(true, 'Invalid value'); + diagnostic.verifyAll(); + }); + + test('PylanceDefaultDiagnosticService cannot handle non-PylanceDefaultDiagnostic diagnostics', async () => { + const diagnostic = typemoq.Mock.ofType(); + diagnostic + .setup((d) => d.code) + .returns(() => DiagnosticCodes.EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic) + .verifiable(typemoq.Times.atLeastOnce()); + + const canHandle = await diagnosticService.canHandle(diagnostic.object); + + expect(canHandle).to.be.equal(false, 'Invalid value'); + diagnostic.verifyAll(); + }); +}); From 953a765524886e255cc5c18014a59dc308955bca Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 28 Apr 2021 17:10:32 -0700 Subject: [PATCH 10/24] Change localization key --- src/client/application/diagnostics/checks/pylanceDefault.ts | 3 ++- src/client/common/utils/localize.ts | 4 ++-- .../diagnostics/checks/pylanceDefault.unit.test.ts | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/client/application/diagnostics/checks/pylanceDefault.ts b/src/client/application/diagnostics/checks/pylanceDefault.ts index a8bf456e63e4..412cd82b41a0 100644 --- a/src/client/application/diagnostics/checks/pylanceDefault.ts +++ b/src/client/application/diagnostics/checks/pylanceDefault.ts @@ -45,7 +45,8 @@ export class PylanceDefaultDiagnosticService extends BaseDiagnosticsService { if (!this.shouldShowPrompt()) { return []; } - return [new PylanceDefaultDiagnostic(Diagnostics.pylanceDefaultLSMessage(), resource)]; + + return [new PylanceDefaultDiagnostic(Diagnostics.pylanceDefaultMessage(), resource)]; } protected async onHandle(diagnostics: IDiagnostic[]): Promise { diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index c7a678cd2fc0..2e49d47504b2 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -67,8 +67,8 @@ export namespace Diagnostics { 'diagnostics.checkIsort5UpgradeGuide', 'We found outdated configuration for sorting imports in this workspace. Check the [isort upgrade guide](https://aka.ms/AA9j5x4) to update your settings.', ); - export const pylanceDefaultLSMessage = localize( - 'Pylance.pylanceDefaultLSMessage', + export const pylanceDefaultMessage = localize( + 'Pylance.pylanceDefaultMessage', 'The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here].\n\nRead Pylance’s license [here]', ); } diff --git a/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts b/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts index e955c9acbe3f..3622f364d471 100644 --- a/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts +++ b/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts @@ -74,7 +74,7 @@ suite('Application Diagnostics - Pylance informational prompt', () => { const diagnostics = await diagnosticService.diagnose(undefined); assert.deepStrictEqual(diagnostics, [ - new PylanceDefaultDiagnostic(Diagnostics.pylanceDefaultLSMessage(), undefined), + new PylanceDefaultDiagnostic(Diagnostics.pylanceDefaultMessage(), undefined), ]); }); From 389b4836f47e6d263bab1a2eaa64aeaeecf6f676 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 28 Apr 2021 17:10:47 -0700 Subject: [PATCH 11/24] Update memento on close instead of just on ok --- .../diagnostics/checks/pylanceDefault.ts | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/client/application/diagnostics/checks/pylanceDefault.ts b/src/client/application/diagnostics/checks/pylanceDefault.ts index 412cd82b41a0..b0bb4fdedc10 100644 --- a/src/client/application/diagnostics/checks/pylanceDefault.ts +++ b/src/client/application/diagnostics/checks/pylanceDefault.ts @@ -59,19 +59,16 @@ export class PylanceDefaultDiagnosticService extends BaseDiagnosticsService { return; } - const options = [ - { - prompt: Common.ok(), - command: { - diagnostic, - invoke: async (): Promise => { - await this.context.globalState.update(PYLANCE_PROMPT_MEMENTO, true); - }, - }, - }, - ]; + const options = [{ prompt: Common.ok() }]; - await this.messageService.handle(diagnostic, { commandPrompts: options }); + await this.messageService.handle(diagnostic, { + commandPrompts: options, + onClose: this.updateMemento.bind(this), + }); + } + + private async updateMemento() { + await this.context.globalState.update(PYLANCE_PROMPT_MEMENTO, true); } private shouldShowPrompt(): boolean { From 54b76d68c30662f193eec3c58a930f4741f1da09 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 28 Apr 2021 17:15:48 -0700 Subject: [PATCH 12/24] Fix localization --- package.nls.json | 2 +- src/client/common/utils/localize.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.nls.json b/package.nls.json index 79a12b6ed920..ccffab7aece0 100644 --- a/package.nls.json +++ b/package.nls.json @@ -56,7 +56,6 @@ "Pylance.pylanceRevertToJediPrompt": "The Pylance extension is not installed but the python.languageServer value is set to \"Pylance\". Would you like to install the Pylance extension to use Pylance, or revert back to Jedi?", "Pylance.pylanceInstallPylance": "Install Pylance", "Pylance.pylanceRevertToJedi": "Revert to Jedi", - "Pylance.pylanceDefaultLSMessage": "The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here].\n\nRead Pylance’s license [here]", "Experiments.inGroup": "User belongs to experiment group '{0}'", "Experiments.optedOutOf": "User opted out of experiment group '{0}'", "Interpreters.RefreshingInterpreters": "Refreshing Python Interpreters", @@ -134,6 +133,7 @@ "diagnostics.checkIsort5UpgradeGuide": "We found outdated configuration for sorting imports in this workspace. Check the [isort upgrade guide](https://aka.ms/AA9j5x4) to update your settings.", "diagnostics.yesUpdateLaunch": "Yes, update launch.json", "diagnostics.invalidTestSettings": "Your settings needs to be updated to change the setting \"python.unitTest.\" to \"python.testing.\", otherwise testing Python code using the extension may not work. Would you like to automatically update your settings now?", + "diagnostics.pylanceDefaultMessage": "The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here].\n\nRead Pylance’s license [here]", "Common.canceled": "Canceled", "Common.cancel": "Cancel", "Common.yesPlease": "Yes, please", diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 2e49d47504b2..2889fe8f1260 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -68,7 +68,7 @@ export namespace Diagnostics { 'We found outdated configuration for sorting imports in this workspace. Check the [isort upgrade guide](https://aka.ms/AA9j5x4) to update your settings.', ); export const pylanceDefaultMessage = localize( - 'Pylance.pylanceDefaultMessage', + 'diagnostics.pylanceDefaultMessage', 'The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here].\n\nRead Pylance’s license [here]', ); } From f0bb2add6a2c9c4edcfd1c47dc68ada365b49e3b Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 29 Apr 2021 16:13:11 -0700 Subject: [PATCH 13/24] Add initialMementoValue handler --- .../application/diagnostics/checks/pylanceDefault.ts | 5 +++-- src/client/common/startPage/startPage.ts | 3 +++ src/client/common/startPage/types.ts | 1 + .../diagnostics/checks/pylanceDefault.unit.test.ts | 9 ++++++--- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/client/application/diagnostics/checks/pylanceDefault.ts b/src/client/application/diagnostics/checks/pylanceDefault.ts index b0bb4fdedc10..0abbef5ac9c3 100644 --- a/src/client/application/diagnostics/checks/pylanceDefault.ts +++ b/src/client/application/diagnostics/checks/pylanceDefault.ts @@ -4,7 +4,7 @@ // eslint-disable-next-line max-classes-per-file import { inject, named } from 'inversify'; import { DiagnosticSeverity } from 'vscode'; -import { EXTENSION_VERSION_MEMENTO } from '../../../common/startPage/startPage'; +import { IStartPage } from '../../../common/startPage/types'; import { IDisposableRegistry, IExtensionContext, Resource } from '../../../common/types'; import { Diagnostics, Common } from '../../../common/utils/localize'; import { IServiceContainer } from '../../../ioc/types'; @@ -33,6 +33,7 @@ export class PylanceDefaultDiagnosticService extends BaseDiagnosticsService { constructor( @inject(IServiceContainer) serviceContainer: IServiceContainer, @inject(IExtensionContext) private readonly context: IExtensionContext, + @inject(IStartPage) private readonly startPage: IStartPage, @inject(IDiagnosticHandlerService) @named(DiagnosticCommandPromptHandlerServiceId) protected readonly messageService: IDiagnosticHandlerService, @@ -72,7 +73,7 @@ export class PylanceDefaultDiagnosticService extends BaseDiagnosticsService { } private shouldShowPrompt(): boolean { - const savedVersion: string | undefined = this.context.globalState.get(EXTENSION_VERSION_MEMENTO); + const savedVersion: string | undefined = this.startPage.initialMementoValue; const promptShown: boolean | undefined = this.context.globalState.get(PYLANCE_PROMPT_MEMENTO); // savedVersion being undefined means that this is the first time the user activates the extension. diff --git a/src/client/common/startPage/startPage.ts b/src/client/common/startPage/startPage.ts index 50d0a7ad2d01..d0cccf72d5b2 100644 --- a/src/client/common/startPage/startPage.ts +++ b/src/client/common/startPage/startPage.ts @@ -41,6 +41,8 @@ export class StartPage extends WebviewPanelHost private actionTakenOnFirstTime = false; private firstTime = false; private webviewDidLoad = false; + public initialMementoValue: string | undefined = undefined; + constructor( @inject(IWebviewPanelProvider) provider: IWebviewPanelProvider, @inject(ICodeCssGenerator) cssGenerator: ICodeCssGenerator, @@ -68,6 +70,7 @@ export class StartPage extends WebviewPanelHost false, ); this.timer = new StopWatch(); + this.initialMementoValue = this.context.globalState.get(EXTENSION_VERSION_MEMENTO); } public async activate(): Promise { diff --git a/src/client/common/startPage/types.ts b/src/client/common/startPage/types.ts index ef50ad1f9189..639dca40232a 100644 --- a/src/client/common/startPage/types.ts +++ b/src/client/common/startPage/types.ts @@ -10,6 +10,7 @@ export type JSONArray = JSONValue[]; export const IStartPage = Symbol('IStartPage'); export interface IStartPage { + readonly initialMementoValue?: string; open(): Promise; extensionVersionChanged(): Promise; } diff --git a/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts b/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts index 3622f364d471..47b215adcd4c 100644 --- a/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts +++ b/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts @@ -21,7 +21,7 @@ import { IDiagnosticHandlerService, IDiagnosticsService, } from '../../../../client/application/diagnostics/types'; -import { EXTENSION_VERSION_MEMENTO } from '../../../../client/common/startPage/startPage'; +import { IStartPage } from '../../../../client/common/startPage/types'; import { IExtensionContext } from '../../../../client/common/types'; import { Diagnostics } from '../../../../client/common/utils/localize'; import { IServiceContainer } from '../../../../client/ioc/types'; @@ -31,6 +31,7 @@ suite('Application Diagnostics - Pylance informational prompt', () => { let diagnosticService: IDiagnosticsService; let filterService: typemoq.IMock; let messageHandler: typemoq.IMock>; + let startPage: typemoq.IMock; let context: typemoq.IMock; let memento: typemoq.IMock; @@ -38,6 +39,7 @@ suite('Application Diagnostics - Pylance informational prompt', () => { serviceContainer = typemoq.Mock.ofType(); filterService = typemoq.Mock.ofType(); messageHandler = typemoq.Mock.ofType>(); + startPage = typemoq.Mock.ofType(); context = typemoq.Mock.ofType(); memento = typemoq.Mock.ofType(); @@ -53,7 +55,7 @@ suite('Application Diagnostics - Pylance informational prompt', () => { BaseDiagnosticsService.handledDiagnosticCodeKeys.shift(); } } - })(serviceContainer.object, context.object, messageHandler.object, []); + })(serviceContainer.object, context.object, startPage.object, messageHandler.object, []); // eslint-disable-next-line @typescript-eslint/no-explicit-any (diagnosticService as any)._clear(); }); @@ -64,7 +66,8 @@ suite('Application Diagnostics - Pylance informational prompt', () => { }); function setupMementos(version?: string, promptShown?: boolean) { - memento.setup((m) => m.get(EXTENSION_VERSION_MEMENTO)).returns(() => version); + startPage.setup((s) => s.initialMementoValue).returns(() => version); + // memento.setup((m) => m.get(EXTENSION_VERSION_MEMENTO)).returns(() => version); memento.setup((m) => m.get(PYLANCE_PROMPT_MEMENTO)).returns(() => promptShown); } From 7d1313cf39ccff83dff3b04ee1687c73c3ff715e Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 29 Apr 2021 16:15:41 -0700 Subject: [PATCH 14/24] Links --- package.nls.json | 2 +- src/client/common/utils/localize.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.nls.json b/package.nls.json index ccffab7aece0..dc53df2fcc37 100644 --- a/package.nls.json +++ b/package.nls.json @@ -133,7 +133,7 @@ "diagnostics.checkIsort5UpgradeGuide": "We found outdated configuration for sorting imports in this workspace. Check the [isort upgrade guide](https://aka.ms/AA9j5x4) to update your settings.", "diagnostics.yesUpdateLaunch": "Yes, update launch.json", "diagnostics.invalidTestSettings": "Your settings needs to be updated to change the setting \"python.unitTest.\" to \"python.testing.\", otherwise testing Python code using the extension may not work. Would you like to automatically update your settings now?", - "diagnostics.pylanceDefaultMessage": "The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here].\n\nRead Pylance’s license [here]", + "diagnostics.pylanceDefaultMessage": "The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here](https://aka.ms/new-python-bundle).\n\nRead Pylance’s license [here](https://marketplace.visualstudio.com/items/ms-python.vscode-pylance/license)", "Common.canceled": "Canceled", "Common.cancel": "Cancel", "Common.yesPlease": "Yes, please", diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 2889fe8f1260..ada9e3b59d2f 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -69,7 +69,7 @@ export namespace Diagnostics { ); export const pylanceDefaultMessage = localize( 'diagnostics.pylanceDefaultMessage', - 'The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here].\n\nRead Pylance’s license [here]', + 'The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here](https://aka.ms/new-python-bundle).\n\nRead Pylance’s license [here](https://marketplace.visualstudio.com/items/ms-python.vscode-pylance/license)', ); } From f3ca8823997738d107a1b06e315b2d935ddcbc55 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 29 Apr 2021 16:33:04 -0700 Subject: [PATCH 15/24] Fix tests --- src/test/startPage/startPage.unit.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/startPage/startPage.unit.test.ts b/src/test/startPage/startPage.unit.test.ts index fd42dfd3944b..85799f349e82 100644 --- a/src/test/startPage/startPage.unit.test.ts +++ b/src/test/startPage/startPage.unit.test.ts @@ -38,7 +38,6 @@ suite('StartPage tests', () => { const dummySettings = new PythonSettings(undefined, new MockAutoSelectionService()); function setupVersions(savedVersion: string, actualVersion: string) { - context.setup((c) => c.globalState).returns(() => memento.object); memento.setup((m) => m.get(typemoq.It.isAnyString())).returns(() => savedVersion); memento .setup((m) => m.update(typemoq.It.isAnyString(), typemoq.It.isAnyString())) @@ -50,7 +49,6 @@ suite('StartPage tests', () => { } function reset() { - context.reset(); memento.reset(); appEnvironment.reset(); } @@ -69,6 +67,7 @@ suite('StartPage tests', () => { appEnvironment = typemoq.Mock.ofType(); memento = typemoq.Mock.ofType(); + context.setup((c) => c.globalState).returns(() => memento.object); configuration.setup((cs) => cs.getSettings(undefined)).returns(() => dummySettings); startPage = new StartPage( From 295973146121412d50fb77231afe050898b4759a Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 29 Apr 2021 17:27:09 -0700 Subject: [PATCH 16/24] Fix tests --- src/test/startPage/startPageIocContainer.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/startPage/startPageIocContainer.ts b/src/test/startPage/startPageIocContainer.ts index 00af74000c56..abba95cc6606 100644 --- a/src/test/startPage/startPageIocContainer.ts +++ b/src/test/startPage/startPageIocContainer.ts @@ -15,6 +15,7 @@ import { ConfigurationChangeEvent, Disposable, EventEmitter, + ExtensionContext, FileSystemWatcher, Uri, WorkspaceFolder, @@ -249,7 +250,9 @@ export class StartPageIocContainer extends UnitTestIocContainer { this.serviceManager.add(IInstallationChannelManager, InstallationChannelManager); + const mockMemento = TypeMoq.Mock.ofType(); const mockExtensionContext = TypeMoq.Mock.ofType(); + mockExtensionContext.setup((m) => m.globalState).returns(() => mockMemento.object); mockExtensionContext.setup((m) => m.globalStoragePath).returns(() => os.tmpdir()); mockExtensionContext.setup((m) => m.extensionPath).returns(() => this.extensionRootPath || os.tmpdir()); this.serviceManager.addSingletonInstance(IExtensionContext, mockExtensionContext.object); From 985f5332b2a215297822fc5c80c8e102a2093572 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Mon, 3 May 2021 11:49:23 -0700 Subject: [PATCH 17/24] Set PYLANCE_PROMPT_MEMENTO to false --- .../application/diagnostics/checks/pylanceDefault.ts | 10 +++++++++- .../diagnostics/checks/pylanceDefault.unit.test.ts | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/client/application/diagnostics/checks/pylanceDefault.ts b/src/client/application/diagnostics/checks/pylanceDefault.ts index 0abbef5ac9c3..c27990078b78 100644 --- a/src/client/application/diagnostics/checks/pylanceDefault.ts +++ b/src/client/application/diagnostics/checks/pylanceDefault.ts @@ -40,6 +40,15 @@ export class PylanceDefaultDiagnosticService extends BaseDiagnosticsService { @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, ) { super([DiagnosticCodes.PylanceDefaultDiagnostic], serviceContainer, disposableRegistry, false); + + const savedVersion: string | undefined = this.startPage.initialMementoValue; + + // savedVersion being undefined means that this is the first time the user activates the extension, + // and we don't want to show the prompt to first-time users. + // We set PYLANCE_PROMPT_MEMENTO to false in case the user reloads the extension and savedVersion becomes set. + if (savedVersion === undefined) { + this.context.globalState.update(PYLANCE_PROMPT_MEMENTO, false); + } } public async diagnose(resource: Resource): Promise { @@ -76,7 +85,6 @@ export class PylanceDefaultDiagnosticService extends BaseDiagnosticsService { const savedVersion: string | undefined = this.startPage.initialMementoValue; const promptShown: boolean | undefined = this.context.globalState.get(PYLANCE_PROMPT_MEMENTO); - // savedVersion being undefined means that this is the first time the user activates the extension. // promptShown being undefined means that this is the first time we check if we should show the prompt. return savedVersion !== undefined && promptShown === undefined; } diff --git a/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts b/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts index 47b215adcd4c..ae8f17807ade 100644 --- a/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts +++ b/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts @@ -89,6 +89,14 @@ suite('Application Diagnostics - Pylance informational prompt', () => { assert.deepStrictEqual(diagnostics, []); }); + test("Should return empty diagnostics if it's an existing installation of the extension and the prompt has been skipped", async () => { + setupMementos('1.0.0', false); + + const diagnostics = await diagnosticService.diagnose(undefined); + + assert.deepStrictEqual(diagnostics, []); + }); + test("Should return empty diagnostics if it's a fresh installation of the extension", async () => { setupMementos(undefined, undefined); From 098a7a998988b3e285809b7f898b4615d168df6e Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Mon, 3 May 2021 11:50:13 -0700 Subject: [PATCH 18/24] Remove unused line --- .../application/diagnostics/checks/pylanceDefault.unit.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts b/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts index ae8f17807ade..2d1359a6e34f 100644 --- a/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts +++ b/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts @@ -67,7 +67,6 @@ suite('Application Diagnostics - Pylance informational prompt', () => { function setupMementos(version?: string, promptShown?: boolean) { startPage.setup((s) => s.initialMementoValue).returns(() => version); - // memento.setup((m) => m.get(EXTENSION_VERSION_MEMENTO)).returns(() => version); memento.setup((m) => m.get(PYLANCE_PROMPT_MEMENTO)).returns(() => promptShown); } From 82286d39481bcb78fe28a11936a4f5b0f80b905d Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Mon, 3 May 2021 13:16:24 -0700 Subject: [PATCH 19/24] Set to true directly --- .../diagnostics/checks/pylanceDefault.ts | 22 +++++++++---------- .../checks/pylanceDefault.unit.test.ts | 8 ------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/client/application/diagnostics/checks/pylanceDefault.ts b/src/client/application/diagnostics/checks/pylanceDefault.ts index c27990078b78..ad310fd13509 100644 --- a/src/client/application/diagnostics/checks/pylanceDefault.ts +++ b/src/client/application/diagnostics/checks/pylanceDefault.ts @@ -40,19 +40,11 @@ export class PylanceDefaultDiagnosticService extends BaseDiagnosticsService { @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, ) { super([DiagnosticCodes.PylanceDefaultDiagnostic], serviceContainer, disposableRegistry, false); - - const savedVersion: string | undefined = this.startPage.initialMementoValue; - - // savedVersion being undefined means that this is the first time the user activates the extension, - // and we don't want to show the prompt to first-time users. - // We set PYLANCE_PROMPT_MEMENTO to false in case the user reloads the extension and savedVersion becomes set. - if (savedVersion === undefined) { - this.context.globalState.update(PYLANCE_PROMPT_MEMENTO, false); - } } public async diagnose(resource: Resource): Promise { - if (!this.shouldShowPrompt()) { + if (!(await this.shouldShowPrompt())) { + await this.updateMemento(); return []; } @@ -81,10 +73,18 @@ export class PylanceDefaultDiagnosticService extends BaseDiagnosticsService { await this.context.globalState.update(PYLANCE_PROMPT_MEMENTO, true); } - private shouldShowPrompt(): boolean { + private async shouldShowPrompt(): Promise { const savedVersion: string | undefined = this.startPage.initialMementoValue; const promptShown: boolean | undefined = this.context.globalState.get(PYLANCE_PROMPT_MEMENTO); + // savedVersion being undefined means that this is the first time the user activates the extension, + // and we don't want to show the prompt to first-time users. + // We set PYLANCE_PROMPT_MEMENTO here to skip the prompt + // in case the user reloads the extension and savedVersion becomes set + if (savedVersion === undefined) { + await this.updateMemento(); + } + // promptShown being undefined means that this is the first time we check if we should show the prompt. return savedVersion !== undefined && promptShown === undefined; } diff --git a/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts b/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts index 2d1359a6e34f..0e2ff7e915e5 100644 --- a/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts +++ b/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts @@ -88,14 +88,6 @@ suite('Application Diagnostics - Pylance informational prompt', () => { assert.deepStrictEqual(diagnostics, []); }); - test("Should return empty diagnostics if it's an existing installation of the extension and the prompt has been skipped", async () => { - setupMementos('1.0.0', false); - - const diagnostics = await diagnosticService.diagnose(undefined); - - assert.deepStrictEqual(diagnostics, []); - }); - test("Should return empty diagnostics if it's a fresh installation of the extension", async () => { setupMementos(undefined, undefined); From 221e8521b7c9caf453cd99b0dcc0152b9ce021a2 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Mon, 3 May 2021 15:10:50 -0700 Subject: [PATCH 20/24] Remove extra updateMemento calls --- src/client/application/diagnostics/checks/pylanceDefault.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/application/diagnostics/checks/pylanceDefault.ts b/src/client/application/diagnostics/checks/pylanceDefault.ts index ad310fd13509..0ed654fcce88 100644 --- a/src/client/application/diagnostics/checks/pylanceDefault.ts +++ b/src/client/application/diagnostics/checks/pylanceDefault.ts @@ -44,7 +44,6 @@ export class PylanceDefaultDiagnosticService extends BaseDiagnosticsService { public async diagnose(resource: Resource): Promise { if (!(await this.shouldShowPrompt())) { - await this.updateMemento(); return []; } @@ -82,7 +81,7 @@ export class PylanceDefaultDiagnosticService extends BaseDiagnosticsService { // We set PYLANCE_PROMPT_MEMENTO here to skip the prompt // in case the user reloads the extension and savedVersion becomes set if (savedVersion === undefined) { - await this.updateMemento(); + return false; } // promptShown being undefined means that this is the first time we check if we should show the prompt. From 7d1036db71a1da4c02212fc8272918b738d6b6ea Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Mon, 3 May 2021 15:13:43 -0700 Subject: [PATCH 21/24] I can't read --- src/client/application/diagnostics/checks/pylanceDefault.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/application/diagnostics/checks/pylanceDefault.ts b/src/client/application/diagnostics/checks/pylanceDefault.ts index 0ed654fcce88..beba5f085b0d 100644 --- a/src/client/application/diagnostics/checks/pylanceDefault.ts +++ b/src/client/application/diagnostics/checks/pylanceDefault.ts @@ -81,10 +81,11 @@ export class PylanceDefaultDiagnosticService extends BaseDiagnosticsService { // We set PYLANCE_PROMPT_MEMENTO here to skip the prompt // in case the user reloads the extension and savedVersion becomes set if (savedVersion === undefined) { + await this.updateMemento(); return false; } // promptShown being undefined means that this is the first time we check if we should show the prompt. - return savedVersion !== undefined && promptShown === undefined; + return promptShown === undefined; } } From adabea9c079e1073bcdf8497c7f4bc3fdbd34e87 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Mon, 3 May 2021 15:14:25 -0700 Subject: [PATCH 22/24] Period --- package.nls.json | 2 +- src/client/common/utils/localize.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.nls.json b/package.nls.json index dc53df2fcc37..e3450302a6b1 100644 --- a/package.nls.json +++ b/package.nls.json @@ -133,7 +133,7 @@ "diagnostics.checkIsort5UpgradeGuide": "We found outdated configuration for sorting imports in this workspace. Check the [isort upgrade guide](https://aka.ms/AA9j5x4) to update your settings.", "diagnostics.yesUpdateLaunch": "Yes, update launch.json", "diagnostics.invalidTestSettings": "Your settings needs to be updated to change the setting \"python.unitTest.\" to \"python.testing.\", otherwise testing Python code using the extension may not work. Would you like to automatically update your settings now?", - "diagnostics.pylanceDefaultMessage": "The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here](https://aka.ms/new-python-bundle).\n\nRead Pylance’s license [here](https://marketplace.visualstudio.com/items/ms-python.vscode-pylance/license)", + "diagnostics.pylanceDefaultMessage": "The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here](https://aka.ms/new-python-bundle).\n\nRead Pylance’s license [here](https://marketplace.visualstudio.com/items/ms-python.vscode-pylance/license).", "Common.canceled": "Canceled", "Common.cancel": "Cancel", "Common.yesPlease": "Yes, please", diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index ada9e3b59d2f..4fd7eca4ec0f 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -69,7 +69,7 @@ export namespace Diagnostics { ); export const pylanceDefaultMessage = localize( 'diagnostics.pylanceDefaultMessage', - 'The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here](https://aka.ms/new-python-bundle).\n\nRead Pylance’s license [here](https://marketplace.visualstudio.com/items/ms-python.vscode-pylance/license)', + 'The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here](https://aka.ms/new-python-bundle).\n\nRead Pylance’s license [here](https://marketplace.visualstudio.com/items/ms-python.vscode-pylance/license).', ); } From 808d47f37810336521914acd235ee7022f898a07 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 4 May 2021 13:06:00 -0700 Subject: [PATCH 23/24] Run in foreground --- src/client/application/diagnostics/checks/pylanceDefault.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/application/diagnostics/checks/pylanceDefault.ts b/src/client/application/diagnostics/checks/pylanceDefault.ts index beba5f085b0d..08b1ca2499e9 100644 --- a/src/client/application/diagnostics/checks/pylanceDefault.ts +++ b/src/client/application/diagnostics/checks/pylanceDefault.ts @@ -39,7 +39,7 @@ export class PylanceDefaultDiagnosticService extends BaseDiagnosticsService { protected readonly messageService: IDiagnosticHandlerService, @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, ) { - super([DiagnosticCodes.PylanceDefaultDiagnostic], serviceContainer, disposableRegistry, false); + super([DiagnosticCodes.PylanceDefaultDiagnostic], serviceContainer, disposableRegistry, true); } public async diagnose(resource: Resource): Promise { From 1c808aadc52d94610dd73c246c26c49a6d15a54d Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 4 May 2021 13:57:46 -0700 Subject: [PATCH 24/24] Add handling test --- .../checks/pylanceDefault.unit.test.ts | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts b/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts index 0e2ff7e915e5..518f46a023f0 100644 --- a/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts +++ b/src/test/application/diagnostics/checks/pylanceDefault.unit.test.ts @@ -23,7 +23,7 @@ import { } from '../../../../client/application/diagnostics/types'; import { IStartPage } from '../../../../client/common/startPage/types'; import { IExtensionContext } from '../../../../client/common/types'; -import { Diagnostics } from '../../../../client/common/utils/localize'; +import { Common, Diagnostics } from '../../../../client/common/utils/localize'; import { IServiceContainer } from '../../../../client/ioc/types'; suite('Application Diagnostics - Pylance informational prompt', () => { @@ -96,6 +96,28 @@ suite('Application Diagnostics - Pylance informational prompt', () => { assert.deepStrictEqual(diagnostics, []); }); + test('Should display a prompt when handling the diagnostic code', async () => { + const diagnostic = new PylanceDefaultDiagnostic(DiagnosticCodes.PylanceDefaultDiagnostic, undefined); + let messagePrompt: MessageCommandPrompt | undefined; + + messageHandler + .setup((f) => f.handle(typemoq.It.isValue(diagnostic), typemoq.It.isAny())) + .callback((_d, prompt: MessageCommandPrompt) => { + messagePrompt = prompt; + }) + .returns(() => Promise.resolve()) + .verifiable(typemoq.Times.once()); + + await diagnosticService.handle([diagnostic]); + + filterService.verifyAll(); + messageHandler.verifyAll(); + + assert.notDeepStrictEqual(messagePrompt, undefined); + assert.notDeepStrictEqual(messagePrompt!.onClose, undefined); + assert.deepStrictEqual(messagePrompt!.commandPrompts, [{ prompt: Common.ok() }]); + }); + test('Should return empty diagnostics if the diagnostic code has been ignored', async () => { const diagnostic = new PylanceDefaultDiagnostic(DiagnosticCodes.PylanceDefaultDiagnostic, undefined);