diff --git a/package.nls.json b/package.nls.json index dfd2206c62c6..a19f2173e26a 100644 --- a/package.nls.json +++ b/package.nls.json @@ -201,6 +201,7 @@ "products.installingModule": "Installing {0}", "OutdatedDebugger.updateDebuggerMessage": "We noticed you are attaching to ptvsd (Python debugger), which was deprecated on May 1st, 2020. Please switch to [debugpy](https://aka.ms/migrateToDebugpy).", "Python27Support.bannerMessage": "The Python extension will have limited support for Python 2.7 starting in the next release, due to changes in external projects we rely on. [Learn more](https://aka.ms/python-27-support).", + "Python27Support.jediMessage": "IntelliSense with Jedi for Python 2.7 is no longer supported. [Learn more](https://aka.ms/python-27-support).", "Jupyter.extensionRequired": "The Jupyter extension is required to perform that task. Click Yes to open the Jupyter extension installation page.", "TensorBoard.missingSourceFile": "We could not locate the requested source file on disk. Please manually specify the file.", "TensorBoard.selectMissingSourceFile": "Choose File", diff --git a/src/client/activation/activationService.ts b/src/client/activation/activationService.ts index 5accd9f1c478..09cb7c13a6de 100644 --- a/src/client/activation/activationService.ts +++ b/src/client/activation/activationService.ts @@ -3,8 +3,7 @@ import '../common/extensions'; import { inject, injectable } from 'inversify'; -import { ConfigurationChangeEvent, ConfigurationTarget, Disposable, OutputChannel, Uri } from 'vscode'; - +import { ConfigurationChangeEvent, Disposable, OutputChannel, Uri } from 'vscode'; import { LSNotSupportedDiagnosticServiceId } from '../application/diagnostics/checks/lsNotSupported'; import { IDiagnosticsService } from '../application/diagnostics/types'; import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types'; @@ -232,8 +231,6 @@ export class LanguageServerExtensionActivationService ): Promise { let serverType = this.getCurrentLanguageServerType(); - this.updateLanguageServerSetting(resource); - if (serverType === LanguageServerType.Microsoft) { const lsNotSupportedDiagnosticService = this.serviceContainer.get( IDiagnosticsService, @@ -345,23 +342,4 @@ export class LanguageServerExtensionActivationService const values = await Promise.all([...this.cache.values()]); values.forEach((v) => (v.clearAnalysisCache ? v.clearAnalysisCache() : noop())); } - - private updateLanguageServerSetting(resource: Resource): void { - // Update settings.json value to Jedi if it's JediLSP. - const settings = this.workspaceService - .getConfiguration('python', resource) - .inspect('languageServer'); - - let configTarget: ConfigurationTarget; - - if (settings?.workspaceValue === LanguageServerType.JediLSP) { - configTarget = ConfigurationTarget.Workspace; - } else if (settings?.globalValue === LanguageServerType.JediLSP) { - configTarget = ConfigurationTarget.Global; - } else { - return; - } - - this.configurationService.updateSetting('languageServer', LanguageServerType.Jedi, resource, configTarget); - } } diff --git a/src/client/application/diagnostics/checks/jediPython27NotSupported.ts b/src/client/application/diagnostics/checks/jediPython27NotSupported.ts new file mode 100644 index 000000000000..460016ea4042 --- /dev/null +++ b/src/client/application/diagnostics/checks/jediPython27NotSupported.ts @@ -0,0 +1,100 @@ +/* eslint-disable max-classes-per-file */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, named } from 'inversify'; +import { ConfigurationTarget, DiagnosticSeverity } from 'vscode'; +import { LanguageServerType } from '../../../activation/types'; +import { IWorkspaceService } from '../../../common/application/types'; +import { IConfigurationService, IDisposableRegistry, Resource } from '../../../common/types'; +import { Common, Python27Support } from '../../../common/utils/localize'; +import { IInterpreterService } from '../../../interpreter/contracts'; +import { IServiceContainer } from '../../../ioc/types'; +import { BaseDiagnostic, BaseDiagnosticsService } from '../base'; +import { IDiagnosticsCommandFactory } from '../commands/types'; +import { DiagnosticCodes } from '../constants'; +import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; +import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; + +export class JediPython27NotSupportedDiagnostic extends BaseDiagnostic { + constructor(message: string, resource: Resource) { + super( + DiagnosticCodes.JediPython27NotSupportedDiagnostic, + message, + DiagnosticSeverity.Warning, + DiagnosticScope.Global, + resource, + ); + } +} + +export const JediPython27NotSupportedDiagnosticServiceId = 'JediPython27NotSupportedDiagnosticServiceId'; + +export class JediPython27NotSupportedDiagnosticService extends BaseDiagnosticsService { + constructor( + @inject(IServiceContainer) serviceContainer: IServiceContainer, + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, + @inject(IConfigurationService) private readonly configurationService: IConfigurationService, + @inject(IDiagnosticHandlerService) + @named(DiagnosticCommandPromptHandlerServiceId) + protected readonly messageService: IDiagnosticHandlerService, + @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, + ) { + super([DiagnosticCodes.JediPython27NotSupportedDiagnostic], serviceContainer, disposableRegistry, true); + } + + public async diagnose(resource: Resource): Promise { + const interpreter = await this.interpreterService.getActiveInterpreter(resource); + + if (interpreter && (interpreter.version?.major ?? 0) < 3) { + return [new JediPython27NotSupportedDiagnostic(Python27Support.jediMessage(), resource)]; + } + + return []; + } + + 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; + } + + this.updateLanguageServerSetting(diagnostic.resource); + + const commandFactory = this.serviceContainer.get(IDiagnosticsCommandFactory); + const options = [ + { + prompt: Common.gotIt(), + }, + { + prompt: Common.doNotShowAgain(), + command: commandFactory.createCommand(diagnostic, { type: 'ignore', options: DiagnosticScope.Global }), + }, + ]; + + await this.messageService.handle(diagnostic, { commandPrompts: options }); + } + + private updateLanguageServerSetting(resource: Resource): void { + // Update settings.json value to Jedi if it's JediLSP. + const settings = this.workspaceService + .getConfiguration('python', resource) + .inspect('languageServer'); + + let configTarget: ConfigurationTarget; + + if (settings?.workspaceValue === LanguageServerType.JediLSP) { + configTarget = ConfigurationTarget.Workspace; + } else if (settings?.globalValue === LanguageServerType.JediLSP) { + configTarget = ConfigurationTarget.Global; + } else { + return; + } + + this.configurationService.updateSetting('languageServer', LanguageServerType.Jedi, resource, configTarget); + } +} diff --git a/src/client/application/diagnostics/constants.ts b/src/client/application/diagnostics/constants.ts index 09e7c16935e4..8f16cc146495 100644 --- a/src/client/application/diagnostics/constants.ts +++ b/src/client/application/diagnostics/constants.ts @@ -21,4 +21,5 @@ export enum DiagnosticCodes { UpgradeCodeRunnerDiagnostic = 'UpgradeCodeRunnerDiagnostic', PylanceDefaultDiagnostic = 'PylanceDefaultDiagnostic', MPLSSurveyDiagnostic = 'MPLSSurveyDiagnostic', + JediPython27NotSupportedDiagnostic = 'JediPython27NotSupportedDiagnostic', } diff --git a/src/client/application/diagnostics/serviceRegistry.ts b/src/client/application/diagnostics/serviceRegistry.ts index 850cbfd10a71..c8dc29e9358f 100644 --- a/src/client/application/diagnostics/serviceRegistry.ts +++ b/src/client/application/diagnostics/serviceRegistry.ts @@ -19,6 +19,10 @@ import { InvalidPythonPathInDebuggerService, InvalidPythonPathInDebuggerServiceId, } from './checks/invalidPythonPathInDebugger'; +import { + JediPython27NotSupportedDiagnosticService, + JediPython27NotSupportedDiagnosticServiceId, +} from './checks/jediPython27NotSupported'; import { LSNotSupportedDiagnosticService, LSNotSupportedDiagnosticServiceId } from './checks/lsNotSupported'; import { InvalidMacPythonInterpreterService, @@ -100,6 +104,12 @@ export function registerTypes(serviceManager: IServiceManager, languageServerTyp PylanceDefaultDiagnosticServiceId, ); + serviceManager.addSingleton( + IDiagnosticsService, + JediPython27NotSupportedDiagnosticService, + JediPython27NotSupportedDiagnosticServiceId, + ); + 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 b2fe967ddf68..4f7a84dd580b 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -548,6 +548,11 @@ export namespace Python27Support { 'Python27Support.bannerMessage', 'The Python extension will have limited support for Python 2.7 starting in the next release, due to changes in external projects we rely on. [Learn more](https://aka.ms/python-27-support).', ); + + export const jediMessage = localize( + 'Python27Support.jediMessage', + 'IntelliSense with Jedi for Python 2.7 is no longer supported. [Learn more](https://aka.ms/python-27-support).', + ); } export namespace MPLSDeprecation { diff --git a/src/test/activation/activationService.unit.test.ts b/src/test/activation/activationService.unit.test.ts index 26a4f7e0200e..8f25897b919d 100644 --- a/src/test/activation/activationService.unit.test.ts +++ b/src/test/activation/activationService.unit.test.ts @@ -3,14 +3,7 @@ import { expect } from 'chai'; import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; -import { - ConfigurationChangeEvent, - ConfigurationTarget, - Disposable, - EventEmitter, - Uri, - WorkspaceConfiguration, -} from 'vscode'; +import { ConfigurationChangeEvent, Disposable, EventEmitter, Uri, WorkspaceConfiguration } from 'vscode'; import { LanguageServerExtensionActivationService } from '../../client/activation/activationService'; import { @@ -81,6 +74,7 @@ suite('Language Server Activation - ActivationService', () => { version: new SemVer('1.2.3'), }; lsNotSupportedDiagnosticService = TypeMoq.Mock.ofType(); + workspaceService.setup((w) => w.hasWorkspaceFolders).returns(() => false); workspaceService.setup((w) => w.workspaceFolders).returns(() => []); configService.setup((c) => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); @@ -660,188 +654,6 @@ suite('Language Server Activation - ActivationService', () => { ); }); - suite('Test JediLSP -> Jedi language server swap', () => { - let serviceContainer: TypeMoq.IMock; - let appShell: TypeMoq.IMock; - let cmdManager: TypeMoq.IMock; - let workspaceService: TypeMoq.IMock; - let platformService: TypeMoq.IMock; - let lsNotSupportedDiagnosticService: TypeMoq.IMock; - let stateFactory: TypeMoq.IMock; - let state: TypeMoq.IMock>; - let interpreterService: TypeMoq.IMock; - let configurationService: TypeMoq.IMock; - - setup(() => { - serviceContainer = TypeMoq.Mock.ofType(); - appShell = TypeMoq.Mock.ofType(); - workspaceService = TypeMoq.Mock.ofType(); - cmdManager = TypeMoq.Mock.ofType(); - platformService = TypeMoq.Mock.ofType(); - stateFactory = TypeMoq.Mock.ofType(); - state = TypeMoq.Mock.ofType>(); - configurationService = TypeMoq.Mock.ofType(); - const extensionsMock = TypeMoq.Mock.ofType(); - lsNotSupportedDiagnosticService = TypeMoq.Mock.ofType(); - workspaceService.setup((w) => w.hasWorkspaceFolders).returns(() => false); - workspaceService.setup((w) => w.workspaceFolders).returns(() => []); - interpreterService = TypeMoq.Mock.ofType(); - state.setup((s) => s.value).returns(() => undefined); - state.setup((s) => s.updateValue(TypeMoq.It.isAny())).returns(() => Promise.resolve()); - const output = TypeMoq.Mock.ofType(); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IOutputChannel), TypeMoq.It.isAny())) - .returns(() => output.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IWorkspaceService))) - .returns(() => workspaceService.object); - serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IApplicationShell))).returns(() => appShell.object); - serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IDisposableRegistry))).returns(() => []); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IConfigurationService))) - .returns(() => configurationService.object); - serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(ICommandManager))).returns(() => cmdManager.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IPlatformService))) - .returns(() => platformService.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IInterpreterService))) - .returns(() => interpreterService.object); - serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IExtensions))).returns(() => extensionsMock.object); - serviceContainer - .setup((s) => - s.get( - TypeMoq.It.isValue(IDiagnosticsService), - TypeMoq.It.isValue(LSNotSupportedDiagnosticServiceId), - ), - ) - .returns(() => lsNotSupportedDiagnosticService.object); - const activator = TypeMoq.Mock.ofType(); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(ILanguageServerActivator), TypeMoq.It.isAny())) - .returns(() => activator.object); - }); - - test('If the workspace language server settings is set to JediLSP, update it to Jedi', async () => { - const resource = Uri.parse('one.py'); - const interpreter = { - version: { major: 3, minor: 8, patch: 0 }, - } as PythonEnvironment; - - configurationService - .setup((c) => c.getSettings(TypeMoq.It.isAny())) - .returns( - () => - ({ - languageServer: LanguageServerType.JediLSP, - languageServerIsDefault: false, - } as PythonSettings), - ); - workspaceService - .setup((ws) => ws.getConfiguration('python', resource)) - .returns( - () => - (({ - inspect: () => ({ - workspaceValue: LanguageServerType.JediLSP, - }), - } as unknown) as WorkspaceConfiguration), - ); - - const activationService = new LanguageServerExtensionActivationService( - serviceContainer.object, - stateFactory.object, - ); - - await activationService.get(resource, interpreter); - - configurationService.verify( - (c) => - c.updateSetting('languageServer', LanguageServerType.Jedi, resource, ConfigurationTarget.Workspace), - TypeMoq.Times.once(), - ); - }); - - test('If the global language server settings is set to JediLSP, update it to Jedi', async () => { - const resource = Uri.parse('one.py'); - const interpreter = { - version: { major: 3, minor: 8, patch: 0 }, - } as PythonEnvironment; - - configurationService - .setup((c) => c.getSettings(TypeMoq.It.isAny())) - .returns( - () => - ({ - languageServer: LanguageServerType.JediLSP, - languageServerIsDefault: false, - } as PythonSettings), - ); - workspaceService - .setup((ws) => ws.getConfiguration('python', resource)) - .returns( - () => - (({ - inspect: () => ({ - globalValue: LanguageServerType.JediLSP, - }), - } as unknown) as WorkspaceConfiguration), - ); - - const activationService = new LanguageServerExtensionActivationService( - serviceContainer.object, - stateFactory.object, - ); - - await activationService.get(resource, interpreter); - - configurationService.verify( - (c) => c.updateSetting('languageServer', LanguageServerType.Jedi, resource, ConfigurationTarget.Global), - TypeMoq.Times.once(), - ); - }); - - test('If no language server settings are set to JediLSP, do nothing', async () => { - const resource = Uri.parse('one.py'); - const interpreter = { - version: { major: 3, minor: 8, patch: 0 }, - } as PythonEnvironment; - - configurationService - .setup((c) => c.getSettings(TypeMoq.It.isAny())) - .returns( - () => - ({ - languageServer: LanguageServerType.JediLSP, - languageServerIsDefault: false, - } as PythonSettings), - ); - workspaceService - .setup((ws) => ws.getConfiguration('python', resource)) - .returns( - () => - (({ - inspect: () => ({ - workspaceValue: LanguageServerType.Node, - }), - } as unknown) as WorkspaceConfiguration), - ); - - const activationService = new LanguageServerExtensionActivationService( - serviceContainer.object, - stateFactory.object, - ); - - await activationService.get(resource, interpreter); - - configurationService.verify( - (c) => - c.updateSetting('languageServer', LanguageServerType.Jedi, resource, ConfigurationTarget.Workspace), - TypeMoq.Times.never(), - ); - }); - }); - suite('Test language server swap when using Python 2.7', () => { let serviceContainer: TypeMoq.IMock; let appShell: TypeMoq.IMock; @@ -867,6 +679,7 @@ suite('Language Server Activation - ActivationService', () => { configurationService = TypeMoq.Mock.ofType(); const extensionsMock = TypeMoq.Mock.ofType(); lsNotSupportedDiagnosticService = TypeMoq.Mock.ofType(); + workspaceService.setup((w) => w.hasWorkspaceFolders).returns(() => false); workspaceService.setup((w) => w.workspaceFolders).returns(() => []); interpreterService = TypeMoq.Mock.ofType(); diff --git a/src/test/application/diagnostics/checks/jediPython27NotSupported.unit.test.ts b/src/test/application/diagnostics/checks/jediPython27NotSupported.unit.test.ts new file mode 100644 index 000000000000..b80b316006c3 --- /dev/null +++ b/src/test/application/diagnostics/checks/jediPython27NotSupported.unit.test.ts @@ -0,0 +1,358 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { ConfigurationTarget, Uri } from 'vscode'; +import { LanguageServerType } from '../../../../client/activation/types'; +import { BaseDiagnosticsService } from '../../../../client/application/diagnostics/base'; +import { + JediPython27NotSupportedDiagnostic, + JediPython27NotSupportedDiagnosticService, +} from '../../../../client/application/diagnostics/checks/jediPython27NotSupported'; +import { IDiagnosticsCommandFactory } from '../../../../client/application/diagnostics/commands/types'; +import { + DiagnosticCommandPromptHandlerService, + MessageCommandPrompt, +} from '../../../../client/application/diagnostics/promptHandler'; +import { + IDiagnosticCommand, + IDiagnosticFilterService, + IDiagnosticHandlerService, +} from '../../../../client/application/diagnostics/types'; +import { IWorkspaceService } from '../../../../client/common/application/types'; +import { WorkspaceService } from '../../../../client/common/application/workspace'; +import { ConfigurationService } from '../../../../client/common/configuration/service'; +import { IConfigurationService } from '../../../../client/common/types'; +import { Python27Support } from '../../../../client/common/utils/localize'; +import { IInterpreterService } from '../../../../client/interpreter/contracts'; +import { IServiceContainer } from '../../../../client/ioc/types'; + +suite('Application Diagnostics - Jedi with Python 2.7 deprecated', () => { + suite('Diagnostics', () => { + const resource = Uri.file('test.py'); + + test('Should return an empty diagnostics array if the active interpreter version is Python 3', async () => { + const interpreterService = { + getActiveInterpreter: () => + Promise.resolve({ + version: { + major: 3, + minor: 8, + patch: 0, + }, + }), + } as IInterpreterService; + + const service = new JediPython27NotSupportedDiagnosticService( + ({ + get: () => ({}), + } as unknown) as IServiceContainer, + interpreterService, + {} as IWorkspaceService, + {} as IConfigurationService, + {} as IDiagnosticHandlerService, + [], + ); + + const result = await service.diagnose(resource); + + assert.strictEqual(result.length, 0); + }); + + test('Should return an empty diagnostics array if the active interpreter is undefined', async () => { + const interpreterService = { + getActiveInterpreter: () => Promise.resolve(undefined), + } as IInterpreterService; + + const service = new JediPython27NotSupportedDiagnosticService( + ({ + get: () => ({}), + } as unknown) as IServiceContainer, + interpreterService, + {} as IWorkspaceService, + {} as IConfigurationService, + {} as IDiagnosticHandlerService, + [], + ); + + const result = await service.diagnose(resource); + + assert.strictEqual(result.length, 0); + }); + + test('Should return a diagnostics array with one diagnostic if the active interpreter version is Python 2.7', async () => { + const interpreterService = { + getActiveInterpreter: () => + Promise.resolve({ + version: { + major: 2, + minor: 7, + patch: 10, + }, + }), + } as IInterpreterService; + + const service = new JediPython27NotSupportedDiagnosticService( + ({ + get: () => ({}), + } as unknown) as IServiceContainer, + interpreterService, + {} as IWorkspaceService, + {} as IConfigurationService, + {} as IDiagnosticHandlerService, + [], + ); + + const result = await service.diagnose(resource); + const diagnostic = result[0]; + + assert.strictEqual(result.length, 1); + assert.strictEqual(diagnostic.message, Python27Support.jediMessage()); + }); + }); + + suite('Handler', () => { + class TestJediPython27NotSupportedDiagnosticService extends JediPython27NotSupportedDiagnosticService { + // eslint-disable-next-line class-methods-use-this + public static clear() { + while (BaseDiagnosticsService.handledDiagnosticCodeKeys.length > 0) { + BaseDiagnosticsService.handledDiagnosticCodeKeys.shift(); + } + } + } + + let services: { + [key: string]: IWorkspaceService | IDiagnosticFilterService | IDiagnosticsCommandFactory; + }; + let serviceContainer: IServiceContainer; + let getConfigurationStub: sinon.SinonStub; + let updateSettingStub: sinon.SinonStub; + let handleMessageStub: sinon.SinonStub; + + const interpreterService = { + getActiveInterpreter: () => + Promise.resolve({ + version: { + major: 2, + minor: 7, + patch: 10, + }, + }), + } as IInterpreterService; + + setup(() => { + services = { + 'Symbol(IDiagnosticsCommandFactory)': { + createCommand: () => ({} as IDiagnosticCommand), + }, + }; + serviceContainer = { + get: (serviceIdentifier: symbol) => + services[serviceIdentifier.toString()] as + | IWorkspaceService + | IDiagnosticFilterService + | IDiagnosticsCommandFactory, + } as IServiceContainer; + + getConfigurationStub = sinon.stub(WorkspaceService.prototype, 'getConfiguration'); + updateSettingStub = sinon.stub(ConfigurationService.prototype, 'updateSetting'); + handleMessageStub = sinon.stub(DiagnosticCommandPromptHandlerService.prototype, 'handle'); + }); + + teardown(() => { + sinon.restore(); + TestJediPython27NotSupportedDiagnosticService.clear(); + }); + + test('Handling an empty diagnostics array does not update the setting and does not display a prompt', async () => { + const service = new TestJediPython27NotSupportedDiagnosticService( + serviceContainer, + interpreterService, + {} as IWorkspaceService, + {} as IConfigurationService, + {} as IDiagnosticHandlerService, + [], + ); + + await service.handle([]); + + sinon.assert.notCalled(handleMessageStub); + sinon.assert.notCalled(getConfigurationStub); + sinon.assert.notCalled(updateSettingStub); + }); + + test('Handling a diagnostic that should be ignored does not update the setting and does not display a prompt', async () => { + const diagnosticHandlerService = new DiagnosticCommandPromptHandlerService(serviceContainer); + + services['Symbol(IDiagnosticFilterService)'] = ({ + shouldIgnoreDiagnostic: async () => Promise.resolve(true), + } as unknown) as IDiagnosticFilterService; + + const service = new TestJediPython27NotSupportedDiagnosticService( + serviceContainer, + interpreterService, + {} as IWorkspaceService, + {} as IConfigurationService, + diagnosticHandlerService, + [], + ); + + await service.handle([new JediPython27NotSupportedDiagnostic('ignored', undefined)]); + + sinon.assert.notCalled(handleMessageStub); + sinon.assert.notCalled(getConfigurationStub); + sinon.assert.notCalled(updateSettingStub); + }); + + test('Handling a diagnostic should show a prompt', async () => { + getConfigurationStub.returns({ + inspect: () => ({ + workspaceValue: LanguageServerType.JediLSP, + }), + }); + const workspaceService = new WorkspaceService(); + services['Symbol(IWorkspaceService)'] = workspaceService; + + const diagnosticHandlerService = new DiagnosticCommandPromptHandlerService(serviceContainer); + const configurationService = new ConfigurationService(serviceContainer); + + services['Symbol(IDiagnosticFilterService)'] = ({ + shouldIgnoreDiagnostic: () => Promise.resolve(false), + } as unknown) as IDiagnosticFilterService; + + const service = new TestJediPython27NotSupportedDiagnosticService( + serviceContainer, + interpreterService, + workspaceService, + configurationService, + diagnosticHandlerService, + [], + ); + + const diagnostic = new JediPython27NotSupportedDiagnostic('diagnostic', undefined); + + await service.handle([diagnostic]); + + sinon.assert.calledOnce(handleMessageStub); + sinon.assert.calledOnce(getConfigurationStub); + sinon.assert.calledOnce(updateSettingStub); + }); + + test('Handling a diagnostic should update the workspace setting if set', async () => { + getConfigurationStub.returns({ + inspect: () => ({ + workspaceValue: LanguageServerType.JediLSP, + }), + }); + const workspaceService = new WorkspaceService(); + services['Symbol(IWorkspaceService)'] = workspaceService; + + const diagnosticHandlerService = new DiagnosticCommandPromptHandlerService(serviceContainer); + const configurationService = new ConfigurationService(serviceContainer); + + services['Symbol(IDiagnosticFilterService)'] = ({ + shouldIgnoreDiagnostic: () => Promise.resolve(false), + } as unknown) as IDiagnosticFilterService; + + const service = new TestJediPython27NotSupportedDiagnosticService( + serviceContainer, + interpreterService, + workspaceService, + configurationService, + diagnosticHandlerService, + [], + ); + + const diagnostic = new JediPython27NotSupportedDiagnostic('diagnostic', undefined); + + await service.handle([diagnostic]); + + sinon.assert.calledOnce(handleMessageStub); + sinon.assert.calledOnce(getConfigurationStub); + sinon.assert.calledWith( + updateSettingStub, + 'languageServer', + LanguageServerType.Jedi, + undefined, + ConfigurationTarget.Workspace, + ); + }); + + test('Handling a diagnostic should update the global setting if set', async () => { + getConfigurationStub.returns({ + inspect: () => ({ + globalValue: LanguageServerType.JediLSP, + }), + }); + const workspaceService = new WorkspaceService(); + services['Symbol(IWorkspaceService)'] = workspaceService; + + const diagnosticHandlerService = new DiagnosticCommandPromptHandlerService(serviceContainer); + const configurationService = new ConfigurationService(serviceContainer); + + services['Symbol(IDiagnosticFilterService)'] = ({ + shouldIgnoreDiagnostic: () => Promise.resolve(false), + } as unknown) as IDiagnosticFilterService; + + const service = new TestJediPython27NotSupportedDiagnosticService( + serviceContainer, + interpreterService, + workspaceService, + configurationService, + diagnosticHandlerService, + [], + ); + + const diagnostic = new JediPython27NotSupportedDiagnostic('diagnostic', undefined); + + await service.handle([diagnostic]); + + sinon.assert.calledOnce(handleMessageStub); + sinon.assert.calledOnce(getConfigurationStub); + sinon.assert.calledWith( + updateSettingStub, + 'languageServer', + LanguageServerType.Jedi, + undefined, + ConfigurationTarget.Global, + ); + }); + + test('Handling a diagnostic should not update the setting if not set in workspace or global scopes', async () => { + getConfigurationStub.returns({ + inspect: () => ({ + workspaceFolderValue: LanguageServerType.JediLSP, + }), + }); + const workspaceService = new WorkspaceService(); + services['Symbol(IWorkspaceService)'] = workspaceService; + + const diagnosticHandlerService = new DiagnosticCommandPromptHandlerService(serviceContainer); + const configurationService = new ConfigurationService(serviceContainer); + + services['Symbol(IDiagnosticFilterService)'] = ({ + shouldIgnoreDiagnostic: () => Promise.resolve(false), + } as unknown) as IDiagnosticFilterService; + + const service = new TestJediPython27NotSupportedDiagnosticService( + serviceContainer, + interpreterService, + workspaceService, + configurationService, + diagnosticHandlerService, + [], + ); + + const diagnostic = new JediPython27NotSupportedDiagnostic('diagnostic', undefined); + + await service.handle([diagnostic]); + + sinon.assert.calledOnce(handleMessageStub); + sinon.assert.calledOnce(getConfigurationStub); + sinon.assert.notCalled(updateSettingStub); + }); + }); +}); diff --git a/src/test/application/diagnostics/serviceRegistry.unit.test.ts b/src/test/application/diagnostics/serviceRegistry.unit.test.ts index e1c44dbfc949..2f8e084d8e50 100644 --- a/src/test/application/diagnostics/serviceRegistry.unit.test.ts +++ b/src/test/application/diagnostics/serviceRegistry.unit.test.ts @@ -18,6 +18,10 @@ import { InvalidPythonPathInDebuggerService, InvalidPythonPathInDebuggerServiceId, } from '../../../client/application/diagnostics/checks/invalidPythonPathInDebugger'; +import { + JediPython27NotSupportedDiagnosticService, + JediPython27NotSupportedDiagnosticServiceId, +} from '../../../client/application/diagnostics/checks/jediPython27NotSupported'; import { LSNotSupportedDiagnosticService, LSNotSupportedDiagnosticServiceId, @@ -114,6 +118,13 @@ suite('Application Diagnostics - Register classes in IOC Container', () => { InvalidPythonPathInDebuggerServiceId, ), ); + verify( + serviceManager.addSingleton( + IDiagnosticsService, + JediPython27NotSupportedDiagnosticService, + JediPython27NotSupportedDiagnosticServiceId, + ), + ); verify( serviceManager.addSingleton( IDiagnosticsService,