diff --git a/src/client/application/diagnostics/base.ts b/src/client/application/diagnostics/base.ts index a1dc8c7722ff..468c3d28c845 100644 --- a/src/client/application/diagnostics/base.ts +++ b/src/client/application/diagnostics/base.ts @@ -5,6 +5,7 @@ import { injectable, unmanaged } from 'inversify'; import { DiagnosticSeverity } from 'vscode'; +import { IWorkspaceService } from '../../common/application/types'; import { Resource } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; import { sendTelemetryEvent } from '../../telemetry'; @@ -21,15 +22,49 @@ export abstract class BaseDiagnostic implements IDiagnostic { @injectable() export abstract class BaseDiagnosticsService implements IDiagnosticsService { + protected static handledDiagnosticCodeKeys: string[] = []; protected readonly filterService: IDiagnosticFilterService; - constructor(@unmanaged() private readonly supportedDiagnosticCodes: string[], - @unmanaged() protected serviceContainer: IServiceContainer) { + constructor( + @unmanaged() private readonly supportedDiagnosticCodes: string[], + @unmanaged() protected serviceContainer: IServiceContainer + ) { this.filterService = serviceContainer.get(IDiagnosticFilterService); } public abstract diagnose(resource: Resource): Promise; - public abstract handle(diagnostics: IDiagnostic[]): Promise; + public async handle(diagnostics: IDiagnostic[]): Promise { + if (diagnostics.length === 0) { + return; + } + const diagnosticsToHandle = diagnostics.filter(item => { + const key = this.getDiagnosticsKey(item); + if (BaseDiagnosticsService.handledDiagnosticCodeKeys.indexOf(key) !== -1) { + return false; + } + BaseDiagnosticsService.handledDiagnosticCodeKeys.push(key); + return true; + }); + await this.onHandle(diagnosticsToHandle); + } public async canHandle(diagnostic: IDiagnostic): Promise { sendTelemetryEvent(EventName.DIAGNOSTICS_MESSAGE, undefined, { code: diagnostic.code }); return this.supportedDiagnosticCodes.filter(item => item === diagnostic.code).length > 0; } + protected abstract onHandle(diagnostics: IDiagnostic[]): Promise; + /** + * Returns a key used to keep track of whether a diagnostic was handled or not. + * So as to prevent handling/displaying messages multiple times for the same diagnostic. + * + * @protected + * @param {IDiagnostic} diagnostic + * @returns {string} + * @memberof BaseDiagnosticsService + */ + protected getDiagnosticsKey(diagnostic: IDiagnostic): string { + if (diagnostic.scope === DiagnosticScope.Global) { + return diagnostic.code; + } + const workspace = this.serviceContainer.get(IWorkspaceService); + const workspaceFolder = diagnostic.resource ? workspace.getWorkspaceFolder(diagnostic.resource) : undefined; + return `${diagnostic.code}dbe75733-0407-4124-a1b2-ca769dc30523${workspaceFolder ? workspaceFolder.uri.fsPath : ''}`; + } } diff --git a/src/client/application/diagnostics/checks/envPathVariable.ts b/src/client/application/diagnostics/checks/envPathVariable.ts index 4fe7e3613f6a..3798691ecbb3 100644 --- a/src/client/application/diagnostics/checks/envPathVariable.ts +++ b/src/client/application/diagnostics/checks/envPathVariable.ts @@ -16,13 +16,19 @@ import { DiagnosticCodes } from '../constants'; import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; -const InvalidEnvPathVariableMessage = 'The environment variable \'{0}\' seems to have some paths containing the \'"\' character.' + +const InvalidEnvPathVariableMessage = + 'The environment variable \'{0}\' seems to have some paths containing the \'"\' character.' + ' The existence of such a character is known to have caused the {1} extension to not load. If the extension fails to load please modify your paths to remove this \'"\' character.'; export class InvalidEnvironmentPathVariableDiagnostic extends BaseDiagnostic { constructor(message: string, resource: Resource) { - super(DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic, - message, DiagnosticSeverity.Warning, DiagnosticScope.Global, resource); + super( + DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic, + message, + DiagnosticSeverity.Warning, + DiagnosticScope.Global, + resource + ); } } @@ -35,20 +41,21 @@ export class EnvironmentPathVariableDiagnosticsService extends BaseDiagnosticsSe constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super([DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic], serviceContainer); this.platform = this.serviceContainer.get(IPlatformService); - this.messageService = serviceContainer.get>(IDiagnosticHandlerService, DiagnosticCommandPromptHandlerServiceId); + this.messageService = serviceContainer.get>( + IDiagnosticHandlerService, + DiagnosticCommandPromptHandlerServiceId + ); } public async diagnose(resource: Resource): Promise { - if (this.platform.isWindows && - this.doesPathVariableHaveInvalidEntries()) { + if (this.platform.isWindows && this.doesPathVariableHaveInvalidEntries()) { const env = this.serviceContainer.get(IApplicationEnvironment); - const message = InvalidEnvPathVariableMessage - .format(this.platform.pathVariableName, env.extensionName); + const message = InvalidEnvPathVariableMessage.format(this.platform.pathVariableName, env.extensionName); return [new InvalidEnvironmentPathVariableDiagnostic(message, resource)]; } else { return []; } } - public async handle(diagnostics: IDiagnostic[]): Promise { + protected async onHandle(diagnostics: IDiagnostic[]): Promise { // This class can only handle one type of diagnostic, hence just use first item in list. if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) { return; diff --git a/src/client/application/diagnostics/checks/invalidDebuggerType.ts b/src/client/application/diagnostics/checks/invalidDebuggerType.ts index 288196ed2820..e748fa1650bb 100644 --- a/src/client/application/diagnostics/checks/invalidDebuggerType.ts +++ b/src/client/application/diagnostics/checks/invalidDebuggerType.ts @@ -17,15 +17,20 @@ import { DiagnosticCodes } from '../constants'; import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; -const InvalidDebuggerTypeMessage = 'Your launch.json file needs to be updated to change the "pythonExperimental" debug ' + +const InvalidDebuggerTypeMessage = + 'Your launch.json file needs to be updated to change the "pythonExperimental" debug ' + 'configurations to use the "python" debugger type, otherwise Python debugging may ' + 'not work. Would you like to automatically update your launch.json file now?'; export class InvalidDebuggerTypeDiagnostic extends BaseDiagnostic { constructor(message: string, resource: Resource) { - super(DiagnosticCodes.InvalidDebuggerTypeDiagnostic, - message, DiagnosticSeverity.Error, DiagnosticScope.WorkspaceFolder, - resource); + super( + DiagnosticCodes.InvalidDebuggerTypeDiagnostic, + message, + DiagnosticSeverity.Error, + DiagnosticScope.WorkspaceFolder, + resource + ); } } @@ -39,7 +44,10 @@ export class InvalidDebuggerTypeDiagnosticsService extends BaseDiagnosticsServic protected readonly fs: IFileSystem; constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super([DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic], serviceContainer); - this.messageService = serviceContainer.get>(IDiagnosticHandlerService, DiagnosticCommandPromptHandlerServiceId); + this.messageService = serviceContainer.get>( + IDiagnosticHandlerService, + DiagnosticCommandPromptHandlerServiceId + ); const cmdManager = serviceContainer.get(ICommandManager); this.fs = this.serviceContainer.get(IFileSystem); cmdManager.registerCommand(CommandName, this.fixLaunchJson, this); @@ -51,7 +59,7 @@ export class InvalidDebuggerTypeDiagnosticsService extends BaseDiagnosticsServic return []; } } - public async handle(diagnostics: IDiagnostic[]): Promise { + protected async onHandle(diagnostics: IDiagnostic[]): Promise { // This class can only handle one type of diagnostic, hence just use first item in list. if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) { return; @@ -61,7 +69,10 @@ export class InvalidDebuggerTypeDiagnosticsService extends BaseDiagnosticsServic const options = [ { prompt: 'Yes, update launch.json', - command: commandFactory.createCommand(diagnostic, { type: 'executeVSCCommand', options: 'python.debugger.replaceExperimental' }) + command: commandFactory.createCommand(diagnostic, { + type: 'executeVSCCommand', + options: 'python.debugger.replaceExperimental' + }) }, { prompt: 'No, I will do it later' @@ -76,7 +87,11 @@ export class InvalidDebuggerTypeDiagnosticsService extends BaseDiagnosticsServic return false; } - const results = await Promise.all(workspaceService.workspaceFolders!.map(workspaceFolder => this.isExperimentalDebuggerUsedInWorkspace(workspaceFolder))); + const results = await Promise.all( + workspaceService.workspaceFolders!.map(workspaceFolder => + this.isExperimentalDebuggerUsedInWorkspace(workspaceFolder) + ) + ); return results.filter(used => used === true).length > 0; } private getLaunchJsonFile(workspaceFolder: WorkspaceFolder) { @@ -84,7 +99,7 @@ export class InvalidDebuggerTypeDiagnosticsService extends BaseDiagnosticsServic } private async isExperimentalDebuggerUsedInWorkspace(workspaceFolder: WorkspaceFolder) { const launchJson = this.getLaunchJsonFile(workspaceFolder); - if (!await this.fs.fileExists(launchJson)) { + if (!(await this.fs.fileExists(launchJson))) { return false; } @@ -97,10 +112,12 @@ export class InvalidDebuggerTypeDiagnosticsService extends BaseDiagnosticsServic return false; } - await Promise.all(workspaceService.workspaceFolders!.map(workspaceFolder => this.fixLaunchJsonInWorkspace(workspaceFolder))); + await Promise.all( + workspaceService.workspaceFolders!.map(workspaceFolder => this.fixLaunchJsonInWorkspace(workspaceFolder)) + ); } private async fixLaunchJsonInWorkspace(workspaceFolder: WorkspaceFolder) { - if (!await this.isExperimentalDebuggerUsedInWorkspace(workspaceFolder)) { + if (!(await this.isExperimentalDebuggerUsedInWorkspace(workspaceFolder))) { return; } diff --git a/src/client/application/diagnostics/checks/invalidPythonPathInDebugger.ts b/src/client/application/diagnostics/checks/invalidPythonPathInDebugger.ts index 4043a8b79883..40cfb22f8a13 100644 --- a/src/client/application/diagnostics/checks/invalidPythonPathInDebugger.ts +++ b/src/client/application/diagnostics/checks/invalidPythonPathInDebugger.ts @@ -19,49 +19,64 @@ import { BaseDiagnostic, BaseDiagnosticsService } from '../base'; import { IDiagnosticsCommandFactory } from '../commands/types'; import { DiagnosticCodes } from '../constants'; import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; -import { DiagnosticScope, IDiagnostic, IDiagnosticCommand, IDiagnosticHandlerService, IInvalidPythonPathInDebuggerService } from '../types'; +import { + DiagnosticScope, + IDiagnostic, + IDiagnosticCommand, + IDiagnosticHandlerService, + IInvalidPythonPathInDebuggerService +} from '../types'; export class InvalidPythonPathInDebuggerSettingsDiagnostic extends BaseDiagnostic { constructor(resource: Resource) { - super(DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic, - Diagnostics.invalidPythonPathInDebuggerSettings(), DiagnosticSeverity.Error, DiagnosticScope.WorkspaceFolder, - resource); + super( + DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic, + Diagnostics.invalidPythonPathInDebuggerSettings(), + DiagnosticSeverity.Error, + DiagnosticScope.WorkspaceFolder, + resource + ); } } export class InvalidPythonPathInDebuggerLaunchDiagnostic extends BaseDiagnostic { constructor(resource: Resource) { - super(DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic, - Diagnostics.invalidPythonPathInDebuggerLaunch(), DiagnosticSeverity.Error, DiagnosticScope.WorkspaceFolder, - resource); + super( + DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic, + Diagnostics.invalidPythonPathInDebuggerLaunch(), + DiagnosticSeverity.Error, + DiagnosticScope.WorkspaceFolder, + resource + ); } } export const InvalidPythonPathInDebuggerServiceId = 'InvalidPythonPathInDebuggerServiceId'; @injectable() -export class InvalidPythonPathInDebuggerService extends BaseDiagnosticsService implements IInvalidPythonPathInDebuggerService { - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer, +export class InvalidPythonPathInDebuggerService extends BaseDiagnosticsService + implements IInvalidPythonPathInDebuggerService { + constructor( + @inject(IServiceContainer) serviceContainer: IServiceContainer, @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, @inject(IDiagnosticsCommandFactory) private readonly commandFactory: IDiagnosticsCommandFactory, @inject(IInterpreterHelper) private readonly interpreterHelper: IInterpreterHelper, @inject(IConfigurationService) private readonly configService: IConfigurationService, - @inject(IDiagnosticHandlerService) @named(DiagnosticCommandPromptHandlerServiceId) protected readonly messageService: IDiagnosticHandlerService) { - super([DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic, DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic], serviceContainer); + @inject(IDiagnosticHandlerService) + @named(DiagnosticCommandPromptHandlerServiceId) + protected readonly messageService: IDiagnosticHandlerService + ) { + super( + [ + DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic, + DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic + ], + serviceContainer + ); } public async diagnose(_resource: Resource): Promise { return []; } - public async handle(diagnostics: IDiagnostic[]): Promise { - // This class can only handle one type of diagnostic, hence just use first item in list. - if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) { - return; - } - const diagnostic = diagnostics[0]; - const commandPrompts = this.getCommandPrompts(diagnostic); - - await this.messageService.handle(diagnostic, { commandPrompts }); - } public async validatePythonPath(pythonPath?: string, resource?: Uri) { pythonPath = pythonPath ? this.resolveVariables(pythonPath, resource) : undefined; let pathInLaunchJson = true; @@ -85,6 +100,16 @@ export class InvalidPythonPathInDebuggerService extends BaseDiagnosticsService i } return false; } + protected async onHandle(diagnostics: IDiagnostic[]): Promise { + // This class can only handle one type of diagnostic, hence just use first item in list. + if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) { + return; + } + const diagnostic = diagnostics[0]; + const commandPrompts = this.getCommandPrompts(diagnostic); + + await this.messageService.handle(diagnostic, { commandPrompts }); + } protected resolveVariables(pythonPath: string, resource: Uri | undefined): string { const workspaceFolder = resource ? this.workspace.getWorkspaceFolder(resource) : undefined; const systemVariables = new SystemVariables(workspaceFolder ? workspaceFolder.uri.fsPath : undefined); @@ -93,22 +118,30 @@ export class InvalidPythonPathInDebuggerService extends BaseDiagnosticsService i private getCommandPrompts(diagnostic: IDiagnostic): { prompt: string; command?: IDiagnosticCommand }[] { switch (diagnostic.code) { case DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic: { - return [{ - prompt: 'Select Python Interpreter', - command: this.commandFactory.createCommand(diagnostic, { type: 'executeVSCCommand', options: 'python.setInterpreter' }) - }]; + return [ + { + prompt: 'Select Python Interpreter', + command: this.commandFactory.createCommand(diagnostic, { + type: 'executeVSCCommand', + options: 'python.setInterpreter' + }) + } + ]; } case DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic: { - return [{ - prompt: 'Open launch.json', - // tslint:disable-next-line:no-object-literal-type-assertion - command: { - diagnostic, invoke: async (): Promise => { - const launchJson = this.getLaunchJsonFile(workspc.workspaceFolders![0]); - await openFile(launchJson); + return [ + { + prompt: 'Open launch.json', + // tslint:disable-next-line:no-object-literal-type-assertion + command: { + diagnostic, + invoke: async (): Promise => { + const launchJson = this.getLaunchJsonFile(workspc.workspaceFolders![0]); + await openFile(launchJson); + } } } - }]; + ]; } default: { throw new Error('Invalid diagnostic for \'InvalidPythonPathInDebuggerService\''); diff --git a/src/client/application/diagnostics/checks/lsNotSupported.ts b/src/client/application/diagnostics/checks/lsNotSupported.ts index 4add329b4865..a504871840b3 100644 --- a/src/client/application/diagnostics/checks/lsNotSupported.ts +++ b/src/client/application/diagnostics/checks/lsNotSupported.ts @@ -17,27 +17,37 @@ import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../type export class LSNotSupportedDiagnostic extends BaseDiagnostic { constructor(message: string, resource: Resource) { - super(DiagnosticCodes.LSNotSupportedDiagnostic, - message, DiagnosticSeverity.Warning, DiagnosticScope.Global, resource); + super( + DiagnosticCodes.LSNotSupportedDiagnostic, + message, + DiagnosticSeverity.Warning, + DiagnosticScope.Global, + resource + ); } } export const LSNotSupportedDiagnosticServiceId = 'LSNotSupportedDiagnosticServiceId'; export class LSNotSupportedDiagnosticService extends BaseDiagnosticsService { - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(ILanguageServerCompatibilityService) private readonly lsCompatibility: ILanguageServerCompatibilityService, - @inject(IDiagnosticHandlerService) @named(DiagnosticCommandPromptHandlerServiceId) protected readonly messageService: IDiagnosticHandlerService) { + constructor( + @inject(IServiceContainer) serviceContainer: IServiceContainer, + @inject(ILanguageServerCompatibilityService) + private readonly lsCompatibility: ILanguageServerCompatibilityService, + @inject(IDiagnosticHandlerService) + @named(DiagnosticCommandPromptHandlerServiceId) + protected readonly messageService: IDiagnosticHandlerService + ) { super([DiagnosticCodes.LSNotSupportedDiagnostic], serviceContainer); } - public async diagnose(resource: Resource): Promise{ + public async diagnose(resource: Resource): Promise { if (await this.lsCompatibility.isSupported()) { return []; - } else{ + } else { return [new LSNotSupportedDiagnostic(Diagnostics.lsNotSupported(), resource)]; } } - public async handle(diagnostics: IDiagnostic[]): Promise{ + protected async onHandle(diagnostics: IDiagnostic[]): Promise { if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) { return; } diff --git a/src/client/application/diagnostics/checks/macPythonInterpreter.ts b/src/client/application/diagnostics/checks/macPythonInterpreter.ts index 4a0051d844eb..92a7bad399ab 100644 --- a/src/client/application/diagnostics/checks/macPythonInterpreter.ts +++ b/src/client/application/diagnostics/checks/macPythonInterpreter.ts @@ -80,22 +80,12 @@ export class InvalidMacPythonInterpreterService extends BaseDiagnosticsService { const interpreters = await this.interpreterService.getInterpreters(resource); if (interpreters.filter(i => !this.helper.isMacDefaultPythonPath(i.path)).length === 0) { - return [ - new InvalidMacPythonInterpreterDiagnostic( - DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic, - resource - ) - ]; + return [new InvalidMacPythonInterpreterDiagnostic(DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic, resource)]; } - return [ - new InvalidMacPythonInterpreterDiagnostic( - DiagnosticCodes.MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic, - resource - ) - ]; + return [new InvalidMacPythonInterpreterDiagnostic(DiagnosticCodes.MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic, resource)]; } - public async handle(diagnostics: IDiagnostic[]): Promise { + protected async onHandle(diagnostics: IDiagnostic[]): Promise { if (diagnostics.length === 0) { return; } diff --git a/src/client/application/diagnostics/checks/powerShellActivation.ts b/src/client/application/diagnostics/checks/powerShellActivation.ts index 61627b55412a..0e259f9c16fe 100644 --- a/src/client/application/diagnostics/checks/powerShellActivation.ts +++ b/src/client/application/diagnostics/checks/powerShellActivation.ts @@ -18,29 +18,41 @@ import { DiagnosticCodes } from '../constants'; import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; -const PowershellActivationNotSupportedWithBatchFilesMessage = 'Activation of the selected Python environment is not supported in PowerShell. Consider changing your shell to Command Prompt.'; +const PowershellActivationNotSupportedWithBatchFilesMessage = + 'Activation of the selected Python environment is not supported in PowerShell. Consider changing your shell to Command Prompt.'; export class PowershellActivationNotAvailableDiagnostic extends BaseDiagnostic { constructor(resource: Resource) { - super(DiagnosticCodes.EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic, + super( + DiagnosticCodes.EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic, PowershellActivationNotSupportedWithBatchFilesMessage, - DiagnosticSeverity.Warning, DiagnosticScope.Global, resource); + DiagnosticSeverity.Warning, + DiagnosticScope.Global, + resource + ); } } -export const PowerShellActivationHackDiagnosticsServiceId = 'EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic'; +export const PowerShellActivationHackDiagnosticsServiceId = + 'EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic'; @injectable() export class PowerShellActivationHackDiagnosticsService extends BaseDiagnosticsService { protected readonly messageService: IDiagnosticHandlerService; constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - super([DiagnosticCodes.EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic], serviceContainer); - this.messageService = serviceContainer.get>(IDiagnosticHandlerService, DiagnosticCommandPromptHandlerServiceId); + super( + [DiagnosticCodes.EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic], + serviceContainer + ); + this.messageService = serviceContainer.get>( + IDiagnosticHandlerService, + DiagnosticCommandPromptHandlerServiceId + ); } public async diagnose(_resource: Resource): Promise { return []; } - public async handle(diagnostics: IDiagnostic[]): Promise { + protected async onHandle(diagnostics: IDiagnostic[]): Promise { // This class can only handle one type of diagnostic, hence just use first item in list. if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) { return; @@ -57,10 +69,14 @@ export class PowerShellActivationHackDiagnosticsService extends BaseDiagnosticsS prompt: 'Use Command Prompt', // tslint:disable-next-line:no-object-literal-type-assertion command: { - diagnostic, invoke: async (): Promise => { - sendTelemetryEvent(EventName.DIAGNOSTICS_ACTION, undefined, { action: 'switchToCommandPrompt' }); - useCommandPromptAsDefaultShell(currentProcess, configurationService) - .catch(ex => Logger.error('Use Command Prompt as default shell', ex)); + diagnostic, + invoke: async (): Promise => { + sendTelemetryEvent(EventName.DIAGNOSTICS_ACTION, undefined, { + action: 'switchToCommandPrompt' + }); + useCommandPromptAsDefaultShell(currentProcess, configurationService).catch(ex => + Logger.error('Use Command Prompt as default shell', ex) + ); } } }, @@ -73,7 +89,10 @@ export class PowerShellActivationHackDiagnosticsService extends BaseDiagnosticsS }, { prompt: 'More Info', - command: commandFactory.createCommand(diagnostic, { type: 'launch', options: 'https://aka.ms/CondaPwsh' }) + command: commandFactory.createCommand(diagnostic, { + type: 'launch', + options: 'https://aka.ms/CondaPwsh' + }) } ]; diff --git a/src/client/application/diagnostics/checks/pythonInterpreter.ts b/src/client/application/diagnostics/checks/pythonInterpreter.ts index 5cbae300e34d..6fa6ba96b53d 100644 --- a/src/client/application/diagnostics/checks/pythonInterpreter.ts +++ b/src/client/application/diagnostics/checks/pythonInterpreter.ts @@ -16,8 +16,10 @@ import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '. import { DiagnosticScope, IDiagnostic, IDiagnosticCommand, IDiagnosticHandlerService } from '../types'; const messages = { - [DiagnosticCodes.NoPythonInterpretersDiagnostic]: 'Python is not installed. Please download and install Python before using the extension.', - [DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic]: 'No Python interpreter is selected. You need to select a Python interpreter to enable features such as IntelliSense, linting, and debugging.' + [DiagnosticCodes.NoPythonInterpretersDiagnostic]: + 'Python is not installed. Please download and install Python before using the extension.', + [DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic]: + 'No Python interpreter is selected. You need to select a Python interpreter to enable features such as IntelliSense, linting, and debugging.' }; export class InvalidPythonInterpreterDiagnostic extends BaseDiagnostic { @@ -35,7 +37,9 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService { [ DiagnosticCodes.NoPythonInterpretersDiagnostic, DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic - ], serviceContainer); + ], + serviceContainer + ); } public async diagnose(resource: Resource): Promise { const configurationService = this.serviceContainer.get(IConfigurationService); @@ -53,38 +57,58 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService { const currentInterpreter = await interpreterService.getActiveInterpreter(resource); if (!currentInterpreter) { - return [new InvalidPythonInterpreterDiagnostic(DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic, resource)]; + return [ + new InvalidPythonInterpreterDiagnostic( + DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic, + resource + ) + ]; } return []; } - public async handle(diagnostics: IDiagnostic[]): Promise { + protected async onHandle(diagnostics: IDiagnostic[]): Promise { if (diagnostics.length === 0) { return; } - const messageService = this.serviceContainer.get>(IDiagnosticHandlerService, DiagnosticCommandPromptHandlerServiceId); - await Promise.all(diagnostics.map(async diagnostic => { - if (!this.canHandle(diagnostic)) { - return; - } - const commandPrompts = this.getCommandPrompts(diagnostic); - return messageService.handle(diagnostic, { commandPrompts, message: diagnostic.message }); - })); + const messageService = this.serviceContainer.get>( + IDiagnosticHandlerService, + DiagnosticCommandPromptHandlerServiceId + ); + await Promise.all( + diagnostics.map(async diagnostic => { + if (!this.canHandle(diagnostic)) { + return; + } + const commandPrompts = this.getCommandPrompts(diagnostic); + return messageService.handle(diagnostic, { commandPrompts, message: diagnostic.message }); + }) + ); } private getCommandPrompts(diagnostic: IDiagnostic): { prompt: string; command?: IDiagnosticCommand }[] { const commandFactory = this.serviceContainer.get(IDiagnosticsCommandFactory); switch (diagnostic.code) { case DiagnosticCodes.NoPythonInterpretersDiagnostic: { - return [{ - prompt: 'Download', - command: commandFactory.createCommand(diagnostic, { type: 'launch', options: 'https://www.python.org/downloads' }) - }]; + return [ + { + prompt: 'Download', + command: commandFactory.createCommand(diagnostic, { + type: 'launch', + options: 'https://www.python.org/downloads' + }) + } + ]; } case DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic: { - return [{ - prompt: 'Select Python Interpreter', - command: commandFactory.createCommand(diagnostic, { type: 'executeVSCCommand', options: 'python.setInterpreter' }) - }]; + return [ + { + prompt: 'Select Python Interpreter', + command: commandFactory.createCommand(diagnostic, { + type: 'executeVSCCommand', + options: 'python.setInterpreter' + }) + } + ]; } default: { throw new Error('Invalid diagnostic for \'InvalidPythonInterpreterService\''); diff --git a/src/client/application/diagnostics/types.ts b/src/client/application/diagnostics/types.ts index 3d0481e1c463..c585fb209885 100644 --- a/src/client/application/diagnostics/types.ts +++ b/src/client/application/diagnostics/types.ts @@ -12,6 +12,11 @@ export enum DiagnosticScope { WorkspaceFolder = 'WorkspaceFolder' } +export enum DiagnosticIgnoreScope { + always = 'always', + session = 'session' +} + export interface IDiagnostic { readonly code: DiagnosticCodes; readonly message: string; diff --git a/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts b/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts index 5b4bdbec4e65..511f0ed7f837 100644 --- a/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts +++ b/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts @@ -7,12 +7,13 @@ import { expect } from 'chai'; import * as path from 'path'; import * as typemoq from 'typemoq'; import { DiagnosticSeverity } from 'vscode'; +import { BaseDiagnosticsService } from '../../../../client/application/diagnostics/base'; import { EnvironmentPathVariableDiagnosticsService } from '../../../../client/application/diagnostics/checks/envPathVariable'; import { CommandOption, IDiagnosticsCommandFactory } from '../../../../client/application/diagnostics/commands/types'; import { DiagnosticCodes } from '../../../../client/application/diagnostics/constants'; import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../../../../client/application/diagnostics/promptHandler'; import { DiagnosticScope, IDiagnostic, IDiagnosticCommand, IDiagnosticFilterService, IDiagnosticHandlerService, IDiagnosticsService } from '../../../../client/application/diagnostics/types'; -import { IApplicationEnvironment } from '../../../../client/common/application/types'; +import { IApplicationEnvironment, IWorkspaceService } from '../../../../client/common/application/types'; import { IPlatformService } from '../../../../client/common/platform/types'; import { ICurrentProcess, IPathUtils } from '../../../../client/common/types'; import { EnvironmentVariables } from '../../../../client/common/variables/types'; @@ -64,8 +65,20 @@ suite('Application Diagnostics - Checks Env Path Variable', () => { pathUtils.setup(p => p.delimiter).returns(() => pathDelimiter); serviceContainer.setup(s => s.get(typemoq.It.isValue(IPathUtils))) .returns(() => pathUtils.object); - - diagnosticService = new EnvironmentPathVariableDiagnosticsService(serviceContainer.object); + const workspaceService = typemoq.Mock.ofType(); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IWorkspaceService))) + .returns(() => workspaceService.object); + workspaceService.setup(w => w.getWorkspaceFolder(typemoq.It.isAny())) + .returns(() => undefined); + + diagnosticService = new class extends EnvironmentPathVariableDiagnosticsService { + public _clear() { + while (BaseDiagnosticsService.handledDiagnosticCodeKeys.length > 0) { + BaseDiagnosticsService.handledDiagnosticCodeKeys.shift(); + } + } + }(serviceContainer.object); + (diagnosticService as any)._clear(); }); test('Can handle EnvPathVariable diagnostics', async () => { diff --git a/src/test/application/diagnostics/checks/invalidPythonPathInDebugger.unit.test.ts b/src/test/application/diagnostics/checks/invalidPythonPathInDebugger.unit.test.ts index e38e7eb9bd60..43d8ec2d25f0 100644 --- a/src/test/application/diagnostics/checks/invalidPythonPathInDebugger.unit.test.ts +++ b/src/test/application/diagnostics/checks/invalidPythonPathInDebugger.unit.test.ts @@ -9,11 +9,20 @@ import { expect } from 'chai'; import * as path from 'path'; import * as typemoq from 'typemoq'; import { Uri } from 'vscode'; +import { BaseDiagnosticsService } from '../../../../client/application/diagnostics/base'; import { InvalidPythonPathInDebuggerService } from '../../../../client/application/diagnostics/checks/invalidPythonPathInDebugger'; import { CommandOption, IDiagnosticsCommandFactory } from '../../../../client/application/diagnostics/commands/types'; import { DiagnosticCodes } from '../../../../client/application/diagnostics/constants'; -import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../../../../client/application/diagnostics/promptHandler'; -import { IDiagnostic, IDiagnosticCommand, IDiagnosticHandlerService, IInvalidPythonPathInDebuggerService } from '../../../../client/application/diagnostics/types'; +import { + DiagnosticCommandPromptHandlerServiceId, + MessageCommandPrompt +} from '../../../../client/application/diagnostics/promptHandler'; +import { + IDiagnostic, + IDiagnosticCommand, + IDiagnosticHandlerService, + IInvalidPythonPathInDebuggerService +} from '../../../../client/application/diagnostics/types'; import { IWorkspaceService } from '../../../../client/common/application/types'; import { IConfigurationService, IPythonSettings } from '../../../../client/common/types'; import { IInterpreterHelper } from '../../../../client/interpreter/contracts'; @@ -29,22 +38,44 @@ suite('Application Diagnostics - Checks Python Path in debugger', () => { setup(() => { const serviceContainer = typemoq.Mock.ofType(); messageHandler = typemoq.Mock.ofType>(); - serviceContainer.setup(s => s.get(typemoq.It.isValue(IDiagnosticHandlerService), typemoq.It.isValue(DiagnosticCommandPromptHandlerServiceId))) + serviceContainer + .setup(s => + s.get( + typemoq.It.isValue(IDiagnosticHandlerService), + typemoq.It.isValue(DiagnosticCommandPromptHandlerServiceId) + ) + ) .returns(() => messageHandler.object); commandFactory = typemoq.Mock.ofType(); - serviceContainer.setup(s => s.get(typemoq.It.isValue(IDiagnosticsCommandFactory))) + serviceContainer + .setup(s => s.get(typemoq.It.isValue(IDiagnosticsCommandFactory))) .returns(() => commandFactory.object); configService = typemoq.Mock.ofType(); - serviceContainer.setup(s => s.get(typemoq.It.isValue(IConfigurationService))) + serviceContainer + .setup(s => s.get(typemoq.It.isValue(IConfigurationService))) .returns(() => configService.object); helper = typemoq.Mock.ofType(); - serviceContainer.setup(s => s.get(typemoq.It.isValue(IInterpreterHelper))) - .returns(() => helper.object); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IInterpreterHelper))).returns(() => helper.object); workspaceService = typemoq.Mock.ofType(); - serviceContainer.setup(s => s.get(typemoq.It.isValue(IWorkspaceService))) + serviceContainer + .setup(s => s.get(typemoq.It.isValue(IWorkspaceService))) .returns(() => workspaceService.object); - diagnosticService = new InvalidPythonPathInDebuggerService(serviceContainer.object, workspaceService.object, commandFactory.object, helper.object, configService.object, messageHandler.object); + diagnosticService = new class extends InvalidPythonPathInDebuggerService { + public _clear() { + while (BaseDiagnosticsService.handledDiagnosticCodeKeys.length > 0) { + BaseDiagnosticsService.handledDiagnosticCodeKeys.shift(); + } + } + }( + serviceContainer.object, + workspaceService.object, + commandFactory.object, + helper.object, + configService.object, + messageHandler.object + ); + (diagnosticService as any)._clear(); }); test('Can handle InvalidPythonPathInDebugger diagnostics', async () => { @@ -53,7 +84,8 @@ suite('Application Diagnostics - Checks Python Path in debugger', () => { DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic ]) { const diagnostic = typemoq.Mock.ofType(); - diagnostic.setup(d => d.code) + diagnostic + .setup(d => d.code) .returns(() => code) .verifiable(typemoq.Times.atLeastOnce()); @@ -78,16 +110,21 @@ suite('Application Diagnostics - Checks Python Path in debugger', () => { }); test('InvalidPythonPathInDebuggerSettings diagnostic should display one option to with a command', async () => { const diagnostic = typemoq.Mock.ofType(); - diagnostic.setup(d => d.code) + diagnostic + .setup(d => d.code) .returns(() => DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic) .verifiable(typemoq.Times.atLeastOnce()); const interpreterSelectionCommand = typemoq.Mock.ofType(); - commandFactory.setup(f => f.createCommand(typemoq.It.isAny(), - typemoq.It.isObjectWith>({ type: 'executeVSCCommand' }))) + commandFactory + .setup(f => + f.createCommand( + typemoq.It.isAny(), + typemoq.It.isObjectWith>({ type: 'executeVSCCommand' }) + ) + ) .returns(() => interpreterSelectionCommand.object) .verifiable(typemoq.Times.once()); - messageHandler.setup(m => m.handle(typemoq.It.isAny(), typemoq.It.isAny())) - .verifiable(typemoq.Times.once()); + messageHandler.setup(m => m.handle(typemoq.It.isAny(), typemoq.It.isAny())).verifiable(typemoq.Times.once()); await diagnosticService.handle([diagnostic.object]); @@ -98,11 +135,13 @@ suite('Application Diagnostics - Checks Python Path in debugger', () => { test('InvalidPythonPathInDebuggerLaunch diagnostic should display one option to with a command', async () => { const diagnostic = typemoq.Mock.ofType(); let options: MessageCommandPrompt | undefined; - diagnostic.setup(d => d.code) + diagnostic + .setup(d => d.code) .returns(() => DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic) .verifiable(typemoq.Times.atLeastOnce()); - messageHandler.setup(m => m.handle(typemoq.It.isAny(), typemoq.It.isAny())) - .callback((_, opts: MessageCommandPrompt) => options = opts) + messageHandler + .setup(m => m.handle(typemoq.It.isAny(), typemoq.It.isAny())) + .callback((_, opts: MessageCommandPrompt) => (options = opts)) .verifiable(typemoq.Times.once()); await diagnosticService.handle([diagnostic.object]); @@ -246,8 +285,11 @@ suite('Application Diagnostics - Checks Python Path in debugger', () => { .returns(() => settings.object) .verifiable(typemoq.Times.never()); let handleInvoked = false; - diagnosticService.handle = (diagnostics) => { - if (diagnostics.length !== 0 && diagnostics[0].code === DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic){ + diagnosticService.handle = diagnostics => { + if ( + diagnostics.length !== 0 && + diagnostics[0].code === DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic + ) { handleInvoked = true; } return Promise.resolve(); @@ -275,8 +317,11 @@ suite('Application Diagnostics - Checks Python Path in debugger', () => { .returns(() => settings.object) .verifiable(typemoq.Times.once()); let handleInvoked = false; - diagnosticService.handle = (diagnostics) => { - if (diagnostics.length !== 0 && diagnostics[0].code === DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic){ + diagnosticService.handle = diagnostics => { + if ( + diagnostics.length !== 0 && + diagnostics[0].code === DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic + ) { handleInvoked = true; } return Promise.resolve(); diff --git a/src/test/application/diagnostics/checks/lsNotSupported.unit.test.ts b/src/test/application/diagnostics/checks/lsNotSupported.unit.test.ts index f2fd01e3d797..1b0f12ec96b8 100644 --- a/src/test/application/diagnostics/checks/lsNotSupported.unit.test.ts +++ b/src/test/application/diagnostics/checks/lsNotSupported.unit.test.ts @@ -6,11 +6,13 @@ import { expect } from 'chai'; import * as TypeMoq from 'typemoq'; import { ILanguageServerCompatibilityService } from '../../../../client/activation/types'; +import { BaseDiagnosticsService } from '../../../../client/application/diagnostics/base'; import { LSNotSupportedDiagnosticService } from '../../../../client/application/diagnostics/checks/lsNotSupported'; import { CommandOption, IDiagnosticsCommandFactory } from '../../../../client/application/diagnostics/commands/types'; import { DiagnosticCodes } from '../../../../client/application/diagnostics/constants'; import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../../../../client/application/diagnostics/promptHandler'; import { DiagnosticScope, IDiagnostic, IDiagnosticCommand, IDiagnosticFilterService, IDiagnosticHandlerService, IDiagnosticsService } from '../../../../client/application/diagnostics/types'; +import { IWorkspaceService } from '../../../../client/common/application/types'; import { IServiceContainer } from '../../../../client/ioc/types'; // tslint:disable:max-func-body-length no-any @@ -30,8 +32,20 @@ suite('Application Diagnostics - Checks LS not supported', () => { serviceContainer.setup(s => s.get(TypeMoq.It.isValue(IDiagnosticFilterService))).returns(() => filterService.object); serviceContainer.setup(s => s.get(TypeMoq.It.isValue(IDiagnosticsCommandFactory))).returns(() => commandFactory.object); serviceContainer.setup(s => s.get(TypeMoq.It.isValue(IDiagnosticHandlerService), TypeMoq.It.isValue(DiagnosticCommandPromptHandlerServiceId))).returns(() => messageHandler.object); + const workspaceService = TypeMoq.Mock.ofType(); + serviceContainer.setup(s => s.get(TypeMoq.It.isValue(IWorkspaceService))) + .returns(() => workspaceService.object); + workspaceService.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())) + .returns(() => undefined); - diagnosticService = new LSNotSupportedDiagnosticService(serviceContainer.object, lsCompatibility.object, messageHandler.object); + diagnosticService = new class extends LSNotSupportedDiagnosticService { + public _clear() { + while (BaseDiagnosticsService.handledDiagnosticCodeKeys.length > 0) { + BaseDiagnosticsService.handledDiagnosticCodeKeys.shift(); + } + } + }(serviceContainer.object, lsCompatibility.object, messageHandler.object); + (diagnosticService as any)._clear(); }); test('Should display two options in message displayed with 2 commands', async () => { diff --git a/src/test/application/diagnostics/checks/powerShellActivation.unit.test.ts b/src/test/application/diagnostics/checks/powerShellActivation.unit.test.ts index dcf1f683de51..fcbc7b52b119 100644 --- a/src/test/application/diagnostics/checks/powerShellActivation.unit.test.ts +++ b/src/test/application/diagnostics/checks/powerShellActivation.unit.test.ts @@ -5,12 +5,23 @@ import { expect } from 'chai'; import * as typemoq from 'typemoq'; +import { BaseDiagnosticsService } from '../../../../client/application/diagnostics/base'; import { PowerShellActivationHackDiagnosticsService } from '../../../../client/application/diagnostics/checks/powerShellActivation'; import { CommandOption, IDiagnosticsCommandFactory } from '../../../../client/application/diagnostics/commands/types'; import { DiagnosticCodes } from '../../../../client/application/diagnostics/constants'; -import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../../../../client/application/diagnostics/promptHandler'; -import { DiagnosticScope, IDiagnostic, IDiagnosticCommand, IDiagnosticFilterService, IDiagnosticHandlerService, IDiagnosticsService } from '../../../../client/application/diagnostics/types'; -import { IApplicationEnvironment } from '../../../../client/common/application/types'; +import { + DiagnosticCommandPromptHandlerServiceId, + MessageCommandPrompt +} from '../../../../client/application/diagnostics/promptHandler'; +import { + DiagnosticScope, + IDiagnostic, + IDiagnosticCommand, + IDiagnosticFilterService, + IDiagnosticHandlerService, + IDiagnosticsService +} from '../../../../client/application/diagnostics/types'; +import { IApplicationEnvironment, IWorkspaceService } from '../../../../client/common/application/types'; import { IPlatformService } from '../../../../client/common/platform/types'; import { ICurrentProcess, IPathUtils } from '../../../../client/common/types'; import { EnvironmentVariables } from '../../../../client/common/variables/types'; @@ -32,43 +43,61 @@ suite('Application Diagnostics - PowerShell Activation', () => { const serviceContainer = typemoq.Mock.ofType(); platformService = typemoq.Mock.ofType(); platformService.setup(p => p.pathVariableName).returns(() => pathVariableName); - serviceContainer.setup(s => s.get(typemoq.It.isValue(IPlatformService))) - .returns(() => platformService.object); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IPlatformService))).returns(() => platformService.object); messageHandler = typemoq.Mock.ofType>(); - serviceContainer.setup(s => s.get(typemoq.It.isValue(IDiagnosticHandlerService), typemoq.It.isValue(DiagnosticCommandPromptHandlerServiceId))) + serviceContainer + .setup(s => + s.get( + typemoq.It.isValue(IDiagnosticHandlerService), + typemoq.It.isValue(DiagnosticCommandPromptHandlerServiceId) + ) + ) .returns(() => messageHandler.object); appEnv = typemoq.Mock.ofType(); appEnv.setup(a => a.extensionName).returns(() => extensionName); - serviceContainer.setup(s => s.get(typemoq.It.isValue(IApplicationEnvironment))) - .returns(() => appEnv.object); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IApplicationEnvironment))).returns(() => appEnv.object); filterService = typemoq.Mock.ofType(); - serviceContainer.setup(s => s.get(typemoq.It.isValue(IDiagnosticFilterService))) + serviceContainer + .setup(s => s.get(typemoq.It.isValue(IDiagnosticFilterService))) .returns(() => filterService.object); commandFactory = typemoq.Mock.ofType(); - serviceContainer.setup(s => s.get(typemoq.It.isValue(IDiagnosticsCommandFactory))) + serviceContainer + .setup(s => s.get(typemoq.It.isValue(IDiagnosticsCommandFactory))) .returns(() => commandFactory.object); const currentProc = typemoq.Mock.ofType(); procEnv = typemoq.Mock.ofType(); currentProc.setup(p => p.env).returns(() => procEnv.object); - serviceContainer.setup(s => s.get(typemoq.It.isValue(ICurrentProcess))) - .returns(() => currentProc.object); + serviceContainer.setup(s => s.get(typemoq.It.isValue(ICurrentProcess))).returns(() => currentProc.object); const pathUtils = typemoq.Mock.ofType(); pathUtils.setup(p => p.delimiter).returns(() => pathDelimiter); - serviceContainer.setup(s => s.get(typemoq.It.isValue(IPathUtils))) - .returns(() => pathUtils.object); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IPathUtils))).returns(() => pathUtils.object); - diagnosticService = new PowerShellActivationHackDiagnosticsService(serviceContainer.object); + const workspaceService = typemoq.Mock.ofType(); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IWorkspaceService))) + .returns(() => workspaceService.object); + workspaceService.setup(w => w.getWorkspaceFolder(typemoq.It.isAny())) + .returns(() => undefined); + + diagnosticService = new class extends PowerShellActivationHackDiagnosticsService { + public _clear() { + while (BaseDiagnosticsService.handledDiagnosticCodeKeys.length > 0) { + BaseDiagnosticsService.handledDiagnosticCodeKeys.shift(); + } + } + }(serviceContainer.object); + (diagnosticService as any)._clear(); }); test('Can handle PowerShell diagnostics', async () => { const diagnostic = typemoq.Mock.ofType(); - diagnostic.setup(d => d.code) + diagnostic + .setup(d => d.code) .returns(() => DiagnosticCodes.EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic) .verifiable(typemoq.Times.atLeastOnce()); @@ -93,21 +122,36 @@ suite('Application Diagnostics - PowerShell Activation', () => { test('Should display three options in message displayed with 4 commands', async () => { const diagnostic = typemoq.Mock.ofType(); let options: MessageCommandPrompt | undefined; - diagnostic.setup(d => d.code) + diagnostic + .setup(d => d.code) .returns(() => DiagnosticCodes.EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic) .verifiable(typemoq.Times.atLeastOnce()); const alwaysIgnoreCommand = typemoq.Mock.ofType(); - commandFactory.setup(f => f.createCommand(typemoq.It.isAny(), - typemoq.It.isObjectWith>({ type: 'ignore', options: DiagnosticScope.Global }))) + commandFactory + .setup(f => + f.createCommand( + typemoq.It.isAny(), + typemoq.It.isObjectWith>({ + type: 'ignore', + options: DiagnosticScope.Global + }) + ) + ) .returns(() => alwaysIgnoreCommand.object) .verifiable(typemoq.Times.once()); const launchBrowserCommand = typemoq.Mock.ofType(); - commandFactory.setup(f => f.createCommand(typemoq.It.isAny(), - typemoq.It.isObjectWith>({ type: 'launch' }))) + commandFactory + .setup(f => + f.createCommand( + typemoq.It.isAny(), + typemoq.It.isObjectWith>({ type: 'launch' }) + ) + ) .returns(() => launchBrowserCommand.object) .verifiable(typemoq.Times.once()); - messageHandler.setup(m => m.handle(typemoq.It.isAny(), typemoq.It.isAny())) - .callback((_, opts: MessageCommandPrompt) => options = opts) + messageHandler + .setup(m => m.handle(typemoq.It.isAny(), typemoq.It.isAny())) + .callback((_, opts: MessageCommandPrompt) => (options = opts)) .verifiable(typemoq.Times.once()); await diagnosticService.handle([diagnostic.object]); diff --git a/src/test/application/diagnostics/checks/pythonInterpreter.unit.test.ts b/src/test/application/diagnostics/checks/pythonInterpreter.unit.test.ts index 468103017484..51be8e8862f8 100644 --- a/src/test/application/diagnostics/checks/pythonInterpreter.unit.test.ts +++ b/src/test/application/diagnostics/checks/pythonInterpreter.unit.test.ts @@ -7,6 +7,7 @@ import { expect } from 'chai'; import * as typemoq from 'typemoq'; +import { BaseDiagnosticsService } from '../../../../client/application/diagnostics/base'; import { InvalidPythonInterpreterDiagnostic, InvalidPythonInterpreterService } from '../../../../client/application/diagnostics/checks/pythonInterpreter'; import { CommandOption, IDiagnosticsCommandFactory } from '../../../../client/application/diagnostics/commands/types'; import { DiagnosticCodes } from '../../../../client/application/diagnostics/constants'; @@ -18,7 +19,7 @@ import { noop } from '../../../../client/common/utils/misc'; import { IInterpreterHelper, IInterpreterService } from '../../../../client/interpreter/contracts'; import { IServiceContainer } from '../../../../client/ioc/types'; -suite('xApplication Diagnostics - Checks Python Interpreter', () => { +suite('Application Diagnostics - Checks Python Interpreter', () => { let diagnosticService: IDiagnosticsService; let messageHandler: typemoq.IMock>; let commandFactory: typemoq.IMock; @@ -58,8 +59,14 @@ suite('xApplication Diagnostics - Checks Python Interpreter', () => { suite('Diagnostics', () => { setup(() => { diagnosticService = new class extends InvalidPythonInterpreterService { + public _clear() { + while (BaseDiagnosticsService.handledDiagnosticCodeKeys.length > 0) { + BaseDiagnosticsService.handledDiagnosticCodeKeys.shift(); + } + } protected addPythonPathChangedHandler() { noop(); } }(createContainer()); + (diagnosticService as any)._clear(); }); test('Can handle InvalidPythonPathInterpreter diagnostics', async () => {