From fac6e1aa42038c343d345bbdc673c4fe884df622 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 26 Oct 2022 13:12:31 -0700 Subject: [PATCH 1/2] Check script extension for current file only --- src/features/DebugSession.ts | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/features/DebugSession.ts b/src/features/DebugSession.ts index fadb91eb59..928d977ba0 100644 --- a/src/features/DebugSession.ts +++ b/src/features/DebugSession.ts @@ -235,34 +235,31 @@ export class DebugSessionFeature extends LanguageClientConsumer } private async resolveLaunchDebugConfiguration(config: DebugConfiguration): Promise { - // Check the languageId only for current documents (which includes untitled documents). + // Check the languageId and file extension only for current documents + // (which includes untitled documents). This prevents accidentally + // running the debugger for an open non-PowerShell file. if (config.current_document) { const currentDocument = vscode.window.activeTextEditor?.document; if (currentDocument?.languageId !== "powershell") { - await vscode.window.showErrorMessage("Please change the current document's language mode to PowerShell."); + void this.logger.writeAndShowError(`PowerShell does not support debugging this language mode: '${currentDocument?.languageId}'.`); return undefined; } - } - // Check the temporary console setting for untitled documents only, and - // check the document extension for if the script is an extant file (it - // could be inline). - if (config.untitled_document) { - if (config.createTemporaryIntegratedConsole) { - await vscode.window.showErrorMessage("Debugging untitled files in a temporary console is not supported."); - return undefined; - } - } else if (config.script) { - // TODO: Why even bother with this complexity? if (await utils.checkIfFileExists(config.script)) { const ext = path.extname(config.script).toLowerCase(); if (!(ext === ".ps1" || ext === ".psm1")) { - await vscode.window.showErrorMessage(`PowerShell does not support debugging this file type: '${path.basename(config.script)}'`); + void this.logger.writeAndShowError(`PowerShell does not support debugging this file type: '${path.basename(config.script)}'.`); return undefined; } } } + // Check the temporary console setting for untitled documents only. + if (config.untitled_document && config.createTemporaryIntegratedConsole) { + void this.logger.writeAndShowError("PowerShell does not support debugging untitled files in a temporary console."); + return undefined; + } + return config; } From ec7b8f1e151f2bbe847e0dc25066d26bcf0310e8 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Wed, 26 Oct 2022 13:43:40 -0700 Subject: [PATCH 2/2] Allow unawaited promises explicitly with `void` And refactor a bit of logging! --- .eslintrc.json | 6 +++ src/features/Console.ts | 4 +- src/features/CustomViews.ts | 6 +-- src/features/DebugSession.ts | 42 +++++++----------- src/features/ExtensionCommands.ts | 41 ++++++++---------- src/features/ExternalApi.ts | 12 +++--- src/features/GetCommands.ts | 7 ++- src/features/NewFileOrProject.ts | 33 ++++++++------ src/features/RemoteFiles.ts | 3 +- src/features/UpdatePowerShell.ts | 3 +- src/languageClientConsumer.ts | 5 +-- src/logging.ts | 21 ++++++--- src/main.ts | 8 ++-- src/process.ts | 23 +++++----- src/session.ts | 72 +++++++++++++++++-------------- test/runTests.ts | 3 +- 16 files changed, 146 insertions(+), 143 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 54639ae29a..221115a29e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -36,6 +36,12 @@ "error", "always" ], + "@typescript-eslint/no-floating-promises": [ + "error", + { + "ignoreVoid": true + } + ], "@typescript-eslint/no-non-null-assertion": [ "off" ], diff --git a/src/features/Console.ts b/src/features/Console.ts index 8262e623f5..279a981127 100644 --- a/src/features/Console.ts +++ b/src/features/Console.ts @@ -170,13 +170,13 @@ export class ConsoleFeature extends LanguageClientConsumer { private commands: vscode.Disposable[]; private handlers: vscode.Disposable[] = []; - constructor(private log: Logger) { + constructor(private logger: Logger) { super(); this.commands = [ vscode.commands.registerCommand("PowerShell.RunSelection", async () => { if (vscode.window.activeTerminal && vscode.window.activeTerminal.name !== "PowerShell Extension") { - this.log.write("PowerShell Extension Terminal is not active! Running in current terminal using 'runSelectedText'"); + this.logger.write("PowerShell Extension Terminal is not active! Running in current terminal using 'runSelectedText'"); await vscode.commands.executeCommand("workbench.action.terminal.runSelectedText"); // We need to honor the focusConsoleOnExecute setting here too. However, the boolean that `show` diff --git a/src/features/CustomViews.ts b/src/features/CustomViews.ts index 31d21cbca2..794962d616 100644 --- a/src/features/CustomViews.ts +++ b/src/features/CustomViews.ts @@ -101,10 +101,8 @@ class PowerShellContentProvider implements vscode.TextDocumentContentProvider { vscode.workspace.textDocuments.some((doc) => { if (doc.uri.toString() === uriString) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - vscode.window.showTextDocument(doc); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - vscode.commands.executeCommand("workbench.action.closeActiveEditor"); + void vscode.window.showTextDocument(doc); + void vscode.commands.executeCommand("workbench.action.closeActiveEditor"); return true; } diff --git a/src/features/DebugSession.ts b/src/features/DebugSession.ts index 928d977ba0..39d90f097a 100644 --- a/src/features/DebugSession.ts +++ b/src/features/DebugSession.ts @@ -81,8 +81,7 @@ export class DebugSessionFeature extends LanguageClientConsumer : this.sessionManager.getSessionDetails(); if (sessionDetails === undefined) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.logger.writeAndShowError(`No session details available for ${session.name}`); + void this.logger.writeAndShowError(`PowerShell session details not available for ${session.name}`); return; } @@ -103,8 +102,7 @@ export class DebugSessionFeature extends LanguageClientConsumer languageClient.onNotification( StartDebuggerNotificationType, // TODO: Use a named debug configuration. - // eslint-disable-next-line @typescript-eslint/no-misused-promises - () => vscode.debug.startDebugging(undefined, { + () => void vscode.debug.startDebugging(undefined, { request: "launch", type: "PowerShell", name: "PowerShell: Interactive Session" @@ -112,8 +110,7 @@ export class DebugSessionFeature extends LanguageClientConsumer languageClient.onNotification( StopDebuggerNotificationType, - // eslint-disable-next-line @typescript-eslint/no-misused-promises - () => vscode.debug.stopDebugging(undefined)) + () => void vscode.debug.stopDebugging(undefined)) ]; } @@ -192,7 +189,7 @@ export class DebugSessionFeature extends LanguageClientConsumer if (config.script === "${file}" || config.script === "${relativeFile}") { if (vscode.window.activeTextEditor === undefined) { - await vscode.window.showErrorMessage("To debug the 'Current File', you must first open a PowerShell script file in the editor."); + void this.logger.writeAndShowError("To debug the 'Current File', you must first open a PowerShell script file in the editor."); return undefined; } config.current_document = true; @@ -218,7 +215,7 @@ export class DebugSessionFeature extends LanguageClientConsumer } else if (config.request === "launch") { resolvedConfig = await this.resolveLaunchDebugConfiguration(config); } else { - await vscode.window.showErrorMessage(`The request type was invalid: '${config.request}'`); + void this.logger.writeAndShowError(`PowerShell debug configuration's request type was invalid: '${config.request}'.`); return null; } @@ -267,13 +264,13 @@ export class DebugSessionFeature extends LanguageClientConsumer const platformDetails = getPlatformDetails(); const versionDetails = this.sessionManager.getPowerShellVersionDetails(); if (versionDetails === undefined) { - await vscode.window.showErrorMessage(`Session version details were not found for ${config.name}`); + void this.logger.writeAndShowError(`PowerShell session version details were not found for '${config.name}'.`); return null; } // Cross-platform attach to process was added in 6.2.0-preview.4. if (versionDetails.version < "7.0.0" && platformDetails.operatingSystem !== OperatingSystem.Windows) { - await vscode.window.showErrorMessage(`Attaching to a PowerShell Host Process on ${OperatingSystem[platformDetails.operatingSystem]} requires PowerShell 7.0 or higher.`); + void this.logger.writeAndShowError(`Attaching to a PowerShell Host Process on ${OperatingSystem[platformDetails.operatingSystem]} requires PowerShell 7.0 or higher.`); return undefined; } @@ -306,10 +303,9 @@ export class SpecifyScriptArgsFeature implements vscode.Disposable { constructor(context: vscode.ExtensionContext) { this.context = context; - this.command = - vscode.commands.registerCommand("PowerShell.SpecifyScriptArgs", () => { - return this.specifyScriptArguments(); - }); + this.command = vscode.commands.registerCommand("PowerShell.SpecifyScriptArgs", () => { + return this.specifyScriptArguments(); + }); } public dispose() { @@ -363,7 +359,7 @@ export class PickPSHostProcessFeature extends LanguageClientConsumer { private waitingForClientToken?: vscode.CancellationTokenSource; private getLanguageClientResolve?: (value: LanguageClient) => void; - constructor() { + constructor(private logger: Logger) { super(); this.command = @@ -398,7 +394,6 @@ export class PickPSHostProcessFeature extends LanguageClientConsumer { (resolve, reject) => { this.getLanguageClientResolve = resolve; - // eslint-disable-next-line @typescript-eslint/no-floating-promises vscode.window .showQuickPick( ["Cancel"], @@ -409,7 +404,7 @@ export class PickPSHostProcessFeature extends LanguageClientConsumer { this.clearWaitingToken(); reject(); } - }); + }, undefined); // Cancel the loading prompt after 60 seconds setTimeout(() => { @@ -417,9 +412,7 @@ export class PickPSHostProcessFeature extends LanguageClientConsumer { this.clearWaitingToken(); reject(); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - vscode.window.showErrorMessage( - "Attach to PowerShell host process: PowerShell session took too long to start."); + void this.logger.writeAndShowError("Attach to PowerShell host process: PowerShell session took too long to start."); } }, 60000); }, @@ -492,7 +485,7 @@ export class PickRunspaceFeature extends LanguageClientConsumer { private waitingForClientToken?: vscode.CancellationTokenSource; private getLanguageClientResolve?: (value: LanguageClient) => void; - constructor() { + constructor(private logger: Logger) { super(); this.command = vscode.commands.registerCommand("PowerShell.PickRunspace", (processId) => { @@ -526,7 +519,6 @@ export class PickRunspaceFeature extends LanguageClientConsumer { (resolve, reject) => { this.getLanguageClientResolve = resolve; - // eslint-disable-next-line @typescript-eslint/no-floating-promises vscode.window .showQuickPick( ["Cancel"], @@ -537,7 +529,7 @@ export class PickRunspaceFeature extends LanguageClientConsumer { this.clearWaitingToken(); reject(); } - }); + }, undefined); // Cancel the loading prompt after 60 seconds setTimeout(() => { @@ -545,9 +537,7 @@ export class PickRunspaceFeature extends LanguageClientConsumer { this.clearWaitingToken(); reject(); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - vscode.window.showErrorMessage( - "Attach to PowerShell host process: PowerShell session took too long to start."); + void this.logger.writeAndShowError("Attach to PowerShell host process: PowerShell session took too long to start."); } }, 60000); }, diff --git a/src/features/ExtensionCommands.ts b/src/features/ExtensionCommands.ts index cd8272b166..c62dde9bc1 100644 --- a/src/features/ExtensionCommands.ts +++ b/src/features/ExtensionCommands.ts @@ -149,7 +149,7 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { private handlers: vscode.Disposable[] = []; private extensionCommands: IExtensionCommand[] = []; - constructor(private log: Logger) { + constructor(private logger: Logger) { super(); this.commands = [ vscode.commands.registerCommand("PowerShell.ShowAdditionalCommands", async () => { @@ -216,7 +216,6 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { this.languageClient.onRequest( InsertTextRequestType, - // eslint-disable-next-line @typescript-eslint/no-floating-promises (details) => this.insertText(details)), this.languageClient.onRequest( @@ -262,8 +261,7 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { // We check to see if they have TrueClear on. If not, no-op because the // overriden Clear-Host already calls [System.Console]::Clear() if (Settings.load().integratedConsole.forceClearScrollbackBuffer) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - vscode.commands.executeCommand("workbench.action.terminal.clear"); + void vscode.commands.executeCommand("workbench.action.terminal.clear"); } }) ]; @@ -292,7 +290,7 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { private async showExtensionCommands(client: LanguageClient): Promise { // If no extension commands are available, show a message if (this.extensionCommands.length === 0) { - await vscode.window.showInformationMessage("No extension commands have been loaded into the current session."); + void this.logger.writeAndShowInformation("No extension commands have been loaded into the current session."); return; } @@ -364,10 +362,10 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { }; } - private newFile(): Thenable { - return vscode.workspace.openTextDocument({ content: "" }) - .then((doc) => vscode.window.showTextDocument(doc)) - .then((_) => EditorOperationResponse.Completed); + private async newFile(): Promise { + const doc = await vscode.workspace.openTextDocument({ content: "" }); + await vscode.window.showTextDocument(doc); + return EditorOperationResponse.Completed; } private openFile(openFileDetails: IOpenFileDetails): Thenable { @@ -416,7 +414,7 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { case "file": { // If the file to save can't be found, just complete the request if (!this.findTextDocument(this.normalizeFilePath(currentFileUri.fsPath))) { - await this.log.writeAndShowError(`File to save not found: ${currentFileUri.fsPath}.`); + void this.logger.writeAndShowError(`File to save not found: ${currentFileUri.fsPath}.`); return EditorOperationResponse.Completed; } @@ -443,8 +441,7 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { if (!saveFileDetails.newPath) { // TODO: Create a class handle vscode warnings and errors so we can warn easily // without logging - await this.log.writeAndShowWarning( - "Cannot save untitled file. Try SaveAs(\"path/to/file.ps1\") instead."); + void this.logger.writeAndShowWarning("Cannot save untitled file. Try SaveAs(\"path/to/file.ps1\") instead."); return EditorOperationResponse.Completed; } @@ -454,7 +451,7 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { } else { // In fresh contexts, workspaceFolders is not defined... if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) { - await this.log.writeAndShowWarning("Cannot save file to relative path: no workspaces are open. " + + void this.logger.writeAndShowWarning("Cannot save file to relative path: no workspaces are open. " + "Try saving to an absolute path, or open a workspace."); return EditorOperationResponse.Completed; } @@ -463,7 +460,7 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { const workspaceRootUri = vscode.workspace.workspaceFolders[0].uri; // We don't support saving to a non-file URI-schemed workspace if (workspaceRootUri.scheme !== "file") { - await this.log.writeAndShowWarning( + void this.logger.writeAndShowWarning( "Cannot save untitled file to a relative path in an untitled workspace. " + "Try saving to an absolute path or opening a workspace folder."); return EditorOperationResponse.Completed; @@ -475,7 +472,7 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { default: { // Other URI schemes are not supported const msg = JSON.stringify(saveFileDetails); - this.log.writeVerbose( + this.logger.writeVerbose( `<${ExtensionCommandsFeature.name}>: Saving a document with scheme '${currentFileUri.scheme}' ` + `is currently unsupported. Message: '${msg}'`); return EditorOperationResponse.Completed; } @@ -503,7 +500,7 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { vscode.Uri.file(destinationAbsolutePath), Buffer.from(oldDocument.getText())); } catch (e) { - await this.log.writeAndShowWarning(`<${ExtensionCommandsFeature.name}>: ` + + void this.logger.writeAndShowWarning(`<${ExtensionCommandsFeature.name}>: ` + `Unable to save file to path '${destinationAbsolutePath}': ${e}`); return; } @@ -572,18 +569,18 @@ export class ExtensionCommandsFeature extends LanguageClientConsumer { return EditorOperationResponse.Completed; } - private async showInformationMessage(message: string): Promise { - await vscode.window.showInformationMessage(message); + private showInformationMessage(message: string): EditorOperationResponse { + void this.logger.writeAndShowInformation(message); return EditorOperationResponse.Completed; } - private async showErrorMessage(message: string): Promise { - await vscode.window.showErrorMessage(message); + private showErrorMessage(message: string): EditorOperationResponse { + void this.logger.writeAndShowError(message); return EditorOperationResponse.Completed; } - private async showWarningMessage(message: string): Promise { - await vscode.window.showWarningMessage(message); + private showWarningMessage(message: string): EditorOperationResponse { + void this.logger.writeAndShowWarning(message); return EditorOperationResponse.Completed; } diff --git a/src/features/ExternalApi.ts b/src/features/ExternalApi.ts index 8194ba3b2b..686ac643bf 100644 --- a/src/features/ExternalApi.ts +++ b/src/features/ExternalApi.ts @@ -39,7 +39,7 @@ export class ExternalApiFeature extends LanguageClientConsumer implements IPower constructor( private extensionContext: vscode.ExtensionContext, private sessionManager: SessionManager, - private log: Logger) { + private logger: Logger) { super(); } @@ -57,13 +57,13 @@ export class ExternalApiFeature extends LanguageClientConsumer implements IPower string session uuid */ public registerExternalExtension(id: string, apiVersion = "v1"): string { - this.log.writeDiagnostic(`Registering extension '${id}' for use with API version '${apiVersion}'.`); + this.logger.writeDiagnostic(`Registering extension '${id}' for use with API version '${apiVersion}'.`); // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const [_name, externalExtension] of ExternalApiFeature.registeredExternalExtension) { if (externalExtension.id === id) { const message = `The extension '${id}' is already registered.`; - this.log.writeWarning(message); + this.logger.writeWarning(message); throw new Error(message); } } @@ -99,7 +99,7 @@ export class ExternalApiFeature extends LanguageClientConsumer implements IPower true if it worked, otherwise throws an error. */ public unregisterExternalExtension(uuid = ""): boolean { - this.log.writeDiagnostic(`Unregistering extension with session UUID: ${uuid}`); + this.logger.writeDiagnostic(`Unregistering extension with session UUID: ${uuid}`); if (!ExternalApiFeature.registeredExternalExtension.delete(uuid)) { throw new Error(`No extension registered with session UUID: ${uuid}`); } @@ -136,7 +136,7 @@ export class ExternalApiFeature extends LanguageClientConsumer implements IPower */ public async getPowerShellVersionDetails(uuid = ""): Promise { const extension = this.getRegisteredExtension(uuid); - this.log.writeDiagnostic(`Extension '${extension.id}' called 'getPowerShellVersionDetails'`); + this.logger.writeDiagnostic(`Extension '${extension.id}' called 'getPowerShellVersionDetails'`); await this.sessionManager.waitUntilStarted(); const versionDetails = this.sessionManager.getPowerShellVersionDetails(); @@ -164,7 +164,7 @@ export class ExternalApiFeature extends LanguageClientConsumer implements IPower */ public async waitUntilStarted(uuid = ""): Promise { const extension = this.getRegisteredExtension(uuid); - this.log.writeDiagnostic(`Extension '${extension.id}' called 'waitUntilStarted'`); + this.logger.writeDiagnostic(`Extension '${extension.id}' called 'waitUntilStarted'`); await this.sessionManager.waitUntilStarted(); } diff --git a/src/features/GetCommands.ts b/src/features/GetCommands.ts index c0c59dd938..7756df75b9 100644 --- a/src/features/GetCommands.ts +++ b/src/features/GetCommands.ts @@ -29,7 +29,7 @@ export class GetCommandsFeature extends LanguageClientConsumer { private commandsExplorerProvider: CommandsExplorerProvider; private commandsExplorerTreeView: vscode.TreeView; - constructor(private log: Logger) { + constructor(private logger: Logger) { super(); this.commands = [ vscode.commands.registerCommand("PowerShell.RefreshCommandsExplorer", @@ -58,14 +58,13 @@ export class GetCommandsFeature extends LanguageClientConsumer { public override setLanguageClient(languageclient: LanguageClient) { this.languageClient = languageclient; if (this.commandsExplorerTreeView.visible) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - vscode.commands.executeCommand("PowerShell.RefreshCommandsExplorer"); + void vscode.commands.executeCommand("PowerShell.RefreshCommandsExplorer"); } } private async CommandExplorerRefresh() { if (this.languageClient === undefined) { - this.log.writeVerbose(`<${GetCommandsFeature.name}>: Unable to send getCommand request`); + this.logger.writeVerbose(`<${GetCommandsFeature.name}>: Unable to send getCommand request`); return; } await this.languageClient.sendRequest(GetCommandRequestType).then((result) => { diff --git a/src/features/NewFileOrProject.ts b/src/features/NewFileOrProject.ts index 3c3047ed1d..fc284e804d 100644 --- a/src/features/NewFileOrProject.ts +++ b/src/features/NewFileOrProject.ts @@ -5,6 +5,7 @@ import vscode = require("vscode"); import { RequestType } from "vscode-languageclient"; import { LanguageClient } from "vscode-languageclient/node"; import { LanguageClientConsumer } from "../languageClientConsumer"; +import { Logger } from "../logging"; export class NewFileOrProjectFeature extends LanguageClientConsumer { @@ -12,7 +13,7 @@ export class NewFileOrProjectFeature extends LanguageClientConsumer { private command: vscode.Disposable; private waitingForClientToken?: vscode.CancellationTokenSource; - constructor() { + constructor(private logger: Logger) { super(); this.command = vscode.commands.registerCommand("PowerShell.NewProjectFromTemplate", async () => { @@ -33,9 +34,7 @@ export class NewFileOrProjectFeature extends LanguageClientConsumer { setTimeout(() => { if (this.waitingForClientToken) { this.clearWaitingToken(); - - // eslint-disable-next-line @typescript-eslint/no-floating-promises - vscode.window.showErrorMessage("New Project: PowerShell session took too long to start."); + void this.logger.writeAndShowError("New Project: PowerShell session took too long to start."); } }, 60000); } else { @@ -53,8 +52,7 @@ export class NewFileOrProjectFeature extends LanguageClientConsumer { if (this.waitingForClientToken) { this.clearWaitingToken(); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.showProjectTemplates(); + void this.showProjectTemplates(); } } @@ -86,7 +84,7 @@ export class NewFileOrProjectFeature extends LanguageClientConsumer { if (response.needsModuleInstall) { // TODO: Offer to install Plaster - await vscode.window.showErrorMessage("Plaster is not installed!"); + void this.logger.writeAndShowError("Plaster is not installed!"); return Promise.reject("Plaster needs to be installed"); } else { let templates = response.templates.map( @@ -137,15 +135,22 @@ export class NewFileOrProjectFeature extends LanguageClientConsumer { if (result?.creationSuccessful) { await this.openWorkspacePath(destinationPath); } else { - await vscode.window.showErrorMessage("Project creation failed, read the Output window for more details."); + void this.logger.writeAndShowError("Project creation failed, read the Output window for more details."); } } else { - const response = await vscode.window.showErrorMessage( - "New Project: You must enter an absolute folder path to continue. Try again?", - "Yes", "No"); - if (response === "Yes") { - await this.createProjectFromTemplate(template); - } + await this.logger.writeAndShowErrorWithActions( + "New Project: You must enter an absolute folder path to continue. Try again?", + [ + { + prompt: "Yes", + action: async () => { await this.createProjectFromTemplate(template); } + }, + { + prompt: "No", + action: undefined + } + ] + ); } } diff --git a/src/features/RemoteFiles.ts b/src/features/RemoteFiles.ts index ee5c184252..cf797c647f 100644 --- a/src/features/RemoteFiles.ts +++ b/src/features/RemoteFiles.ts @@ -72,7 +72,6 @@ export class RemoteFilesFeature extends LanguageClientConsumer { return await innerCloseFiles(); } - // eslint-disable-next-line @typescript-eslint/no-floating-promises - innerCloseFiles(); + void innerCloseFiles(); } } diff --git a/src/features/UpdatePowerShell.ts b/src/features/UpdatePowerShell.ts index 28e1f3663d..45ef47c801 100644 --- a/src/features/UpdatePowerShell.ts +++ b/src/features/UpdatePowerShell.ts @@ -168,8 +168,7 @@ export async function InvokePowerShellUpdateCheck( msi.on("close", () => { // Now that the MSI is finished, restart the session. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - sessionManager.start(); + void sessionManager.start(); fs.unlinkSync(msiDownloadPath); }); diff --git a/src/languageClientConsumer.ts b/src/languageClientConsumer.ts index 4ff998d0c2..ddbceeb97e 100644 --- a/src/languageClientConsumer.ts +++ b/src/languageClientConsumer.ts @@ -16,9 +16,8 @@ export abstract class LanguageClientConsumer { public get languageClient(): LanguageClient | undefined { if (!this._languageClient) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - window.showInformationMessage( - "PowerShell extension has not finished starting up yet. Please try again in a few moments."); + // TODO: Plumb through the logger. + void window.showInformationMessage("PowerShell extension has not finished starting up yet. Please try again in a few moments."); } return this._languageClient; } diff --git a/src/logging.ts b/src/logging.ts index cf3f68d907..c007088374 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -62,12 +62,10 @@ export class Logger implements ILogger { private writeAtLevel(logLevel: LogLevel, message: string, ...additionalMessages: string[]): void { if (logLevel >= this.MinimumLogLevel) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.writeLine(message, logLevel); + void this.writeLine(message, logLevel); for (const additionalMessage of additionalMessages) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.writeLine(additionalMessage, logLevel); + void this.writeLine(additionalMessage, logLevel); } } } @@ -76,6 +74,15 @@ export class Logger implements ILogger { this.writeAtLevel(LogLevel.Normal, message, ...additionalMessages); } + public async writeAndShowInformation(message: string, ...additionalMessages: string[]): Promise { + this.write(message, ...additionalMessages); + + const selection = await vscode.window.showInformationMessage(message, "Show Logs"); + if (selection !== undefined) { + this.showLogPanel(); + } + } + public writeDiagnostic(message: string, ...additionalMessages: string[]): void { this.writeAtLevel(LogLevel.Diagnostic, message, ...additionalMessages); } @@ -112,7 +119,7 @@ export class Logger implements ILogger { public async writeAndShowErrorWithActions( message: string, - actions: { prompt: string; action: () => Promise }[]): Promise { + actions: { prompt: string; action: (() => Promise) | undefined }[]): Promise { this.writeError(message); const fullActions = [ @@ -125,8 +132,8 @@ export class Logger implements ILogger { const choice = await vscode.window.showErrorMessage(message, ...actionKeys); if (choice) { for (const action of fullActions) { - if (choice === action.prompt) { - action.action(); + if (choice === action.prompt && action.action !== undefined ) { + await action.action(); return; } } diff --git a/src/main.ts b/src/main.ts index 190348cb98..36849e4878 100644 --- a/src/main.ts +++ b/src/main.ts @@ -56,7 +56,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { - const editorServicesLogPath = this.log.getLogFilePath(logFileName); + const editorServicesLogPath = this.logger.getLogFilePath(logFileName); const psesModulePath = path.resolve( @@ -94,7 +94,7 @@ export class PowerShellProcess { Buffer.from(startEditorServices, "utf16le").toString("base64")); } - this.log.write( + this.logger.write( "Language server starting --", " PowerShell executable: " + this.exePath, " PowerShell args: " + powerShellArgs.join(" "), @@ -117,7 +117,7 @@ export class PowerShellProcess { this.consoleTerminal = vscode.window.createTerminal(terminalOptions); const pwshName = path.basename(this.exePath); - this.log.write(`${pwshName} started.`); + this.logger.write(`${pwshName} started.`); if (this.sessionSettings.integratedConsole.showOnStartup && !this.sessionSettings.integratedConsole.startInBackground) { @@ -144,7 +144,7 @@ export class PowerShellProcess { public async dispose() { // Clean up the session file - this.log.write("Terminating PowerShell process..."); + this.logger.write("Terminating PowerShell process..."); await PowerShellProcess.deleteSessionFile(this.sessionFilePath); @@ -164,7 +164,7 @@ export class PowerShellProcess { } private logTerminalPid(pid: number, exeName: string) { - this.log.write(`${exeName} PID: ${pid}`); + this.logger.write(`${exeName} PID: ${pid}`); } private isLoginShell(pwshPath: string): boolean { @@ -200,18 +200,17 @@ export class PowerShellProcess { const warnAt = numOfTries - PowerShellProcess.warnUserThreshold; // Check every 2 seconds - this.log.write("Waiting for session file..."); + this.logger.write("Waiting for session file..."); for (let i = numOfTries; i > 0; i--) { if (await utils.checkIfFileExists(this.sessionFilePath)) { - this.log.write("Session file found!"); + this.logger.write("Session file found!"); const sessionDetails = await PowerShellProcess.readSessionFile(this.sessionFilePath); await PowerShellProcess.deleteSessionFile(this.sessionFilePath); return sessionDetails; } if (warnAt === i) { - await vscode.window.showWarningMessage(`Loading the PowerShell extension is taking longer than expected. - If you're using privilege enforcement software, this can affect start up performance.`); + void this.logger.writeAndShowWarning("Loading the PowerShell extension is taking longer than expected. If you're using privilege enforcement software, this can affect start up performance."); } // Wait a bit and try again @@ -219,7 +218,7 @@ export class PowerShellProcess { } const err = "Timed out waiting for session file to appear!"; - this.log.write(err); + this.logger.write(err); throw new Error(err); } @@ -228,7 +227,7 @@ export class PowerShellProcess { return; } - this.log.write("powershell.exe terminated or terminal UI was closed"); + this.logger.write("powershell.exe terminated or terminal UI was closed"); this.onExitedEmitter.fire(); } } diff --git a/src/session.ts b/src/session.ts index c1de1fcf0c..c3fcb3848b 100644 --- a/src/session.ts +++ b/src/session.ts @@ -103,7 +103,7 @@ export class SessionManager implements Middleware { constructor( private extensionContext: vscode.ExtensionContext, private sessionSettings: Settings.ISettings, - private log: Logger, + private logger: Logger, private documentSelector: DocumentSelector, hostName: string, hostVersion: string, @@ -119,7 +119,7 @@ export class SessionManager implements Middleware { const osBitness = this.platformDetails.isOS64Bit ? "64-bit" : "32-bit"; const procBitness = this.platformDetails.isProcess64Bit ? "64-bit" : "32-bit"; - this.log.write( + this.logger.write( `Visual Studio Code v${vscode.version} ${procBitness}`, `${this.HostName} Extension v${this.HostVersion}`, `Operating System: ${OperatingSystem[this.platformDetails.operatingSystem]} ${osBitness}`); @@ -163,7 +163,7 @@ export class SessionManager implements Middleware { } // Create a folder for the session files. await vscode.workspace.fs.createDirectory(this.sessionsFolder); - await this.log.startNewLog(this.sessionSettings.developer.editorServicesLogLevel); + await this.logger.startNewLog(this.sessionSettings.developer.editorServicesLogLevel); await this.promptPowerShellExeSettingsCleanup(); await this.migrateWhitespaceAroundPipeSetting(); this.PowerShellExeDetails = await this.findPowerShell(); @@ -174,7 +174,7 @@ export class SessionManager implements Middleware { } public async stop() { - this.log.write("Shutting down language client..."); + this.logger.write("Shutting down language client..."); try { if (this.sessionStatus === SessionStatus.Failed) { @@ -258,7 +258,7 @@ export class SessionManager implements Middleware { this.PowerShellExeDetails.exePath, bundledModulesPath, "[TEMP] PowerShell Extension", - this.log, + this.logger, this.buildEditorServicesArgs(bundledModulesPath, this.PowerShellExeDetails) + "-DebugServiceOnly ", this.getNewSessionFilePath(), this.sessionSettings); @@ -293,7 +293,7 @@ export class SessionManager implements Middleware { if (codeLensToFix.command?.command === "editor.action.showReferences") { const oldArgs = codeLensToFix.command.arguments; if (oldArgs === undefined || oldArgs.length < 3) { - this.log.writeError("Code Lens arguments were malformed"); + this.logger.writeError("Code Lens arguments were malformed"); return codeLensToFix; } @@ -353,6 +353,7 @@ export class SessionManager implements Middleware { } } + // TODO: Remove this migration code. private async promptPowerShellExeSettingsCleanup() { if (!this.sessionSettings.powerShellExePath) { // undefined or null return; @@ -421,7 +422,7 @@ export class SessionManager implements Middleware { private async startPowerShell(): Promise { if (this.PowerShellExeDetails === undefined) { - await this.setSessionFailure("Unable to find PowerShell."); + this.setSessionFailure("Unable to find PowerShell."); return; } @@ -433,7 +434,7 @@ export class SessionManager implements Middleware { this.PowerShellExeDetails.exePath, bundledModulesPath, "PowerShell Extension", - this.log, + this.logger, this.buildEditorServicesArgs(bundledModulesPath, this.PowerShellExeDetails), this.getNewSessionFilePath(), this.sessionSettings); @@ -449,31 +450,31 @@ export class SessionManager implements Middleware { try { this.sessionDetails = await languageServerProcess.start("EditorServices"); } catch (err) { - await this.setSessionFailure("PowerShell process failed to start: ", err instanceof Error ? err.message : "unknown"); + this.setSessionFailure("PowerShell process failed to start: ", err instanceof Error ? err.message : "unknown"); } if (this.sessionDetails?.status === "started") { - this.log.write("Language server started."); + this.logger.write("Language server started."); try { await this.startLanguageClient(this.sessionDetails); } catch (err) { - await this.setSessionFailure("Language client failed to start: ", err instanceof Error ? err.message : "unknown"); + this.setSessionFailure("Language client failed to start: ", err instanceof Error ? err.message : "unknown"); } } else if (this.sessionDetails?.status === "failed") { if (this.sessionDetails.reason === "unsupported") { - await this.setSessionFailure( + this.setSessionFailure( "PowerShell language features are only supported on PowerShell version 5.1 and 7+. " + `The current version is ${this.sessionDetails.powerShellVersion}.`); } else if (this.sessionDetails.reason === "languageMode") { - await this.setSessionFailure( + this.setSessionFailure( "PowerShell language features are disabled due to an unsupported LanguageMode: " + `${this.sessionDetails.detail}`); } else { - await this.setSessionFailure( + this.setSessionFailure( `PowerShell could not be started for an unknown reason '${this.sessionDetails.reason}'`); } } else { - await this.setSessionFailure( + this.setSessionFailure( `Unknown session status '${this.sessionDetails?.status}' with reason '${this.sessionDetails?.reason}`); } @@ -500,7 +501,7 @@ export class SessionManager implements Middleware { } foundPowerShell = defaultPowerShell ?? await powershellExeFinder.getFirstAvailablePowerShellInstallation(); } catch (e) { - this.log.writeError(`Error occurred while searching for a PowerShell executable:\n${e}`); + this.logger.writeError(`Error occurred while searching for a PowerShell executable:\n${e}`); } if (foundPowerShell === undefined) { @@ -509,7 +510,7 @@ export class SessionManager implements Middleware { + " You can also configure custom PowerShell installations" + " with the 'powershell.powerShellAdditionalExePaths' setting."; - await this.log.writeAndShowErrorWithActions(message, [ + await this.logger.writeAndShowErrorWithActions(message, [ { prompt: "Get PowerShell", action: async () => { @@ -533,7 +534,7 @@ export class SessionManager implements Middleware { if (await utils.checkIfDirectoryExists(path.join(devBundledModulesPath, "PowerShellEditorServices/bin"))) { bundledModulesPath = devBundledModulesPath; } else { - this.log.write( + this.logger.write( "\nWARNING: In development mode but PowerShellEditorServices dev module path cannot be " + `found (or has not been built yet): ${devBundledModulesPath}\n`); } @@ -582,13 +583,19 @@ Type 'help' to get help. } private async promptForRestart() { - const response = await vscode.window.showErrorMessage( + await this.logger.writeAndShowErrorWithActions( "The PowerShell Extension Terminal has stopped, would you like to restart it? IntelliSense and other features will not work without it!", - "Yes", "No"); - - if (response === "Yes") { - await this.restartSession(); - } + [ + { + prompt: "Yes", + action: async () => { await this.restartSession(); } + }, + { + prompt: "No", + action: undefined + } + ] + ); } private sendTelemetryEvent(eventName: string, properties?: TelemetryEventProperties, measures?: TelemetryEventMeasurements) { @@ -598,8 +605,8 @@ Type 'help' to get help. } private async startLanguageClient(sessionDetails: IEditorServicesSessionDetails) { - this.log.write(`Connecting to language service on pipe: ${sessionDetails.languageServicePipeName}`); - this.log.write("Session details: " + JSON.stringify(sessionDetails)); + this.logger.write(`Connecting to language service on pipe: ${sessionDetails.languageServicePipeName}`); + this.logger.write("Session details: " + JSON.stringify(sessionDetails)); const connectFunc = () => { return new Promise( @@ -608,7 +615,7 @@ Type 'help' to get help. socket.on( "connect", () => { - this.log.write("Language service socket connected."); + this.logger.write("Language service socket connected."); resolve({ writer: socket, reader: socket }); }); }); @@ -686,7 +693,7 @@ Type 'help' to get help. try { await this.languageClient.start(); } catch (err) { - await this.setSessionFailure("Could not start language service: ", err instanceof Error ? err.message : "unknown"); + this.setSessionFailure("Could not start language service: ", err instanceof Error ? err.message : "unknown"); return; } @@ -698,8 +705,7 @@ Type 'help' to get help. this.started = true; // NOTE: We specifically don't want to wait for this. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.checkForPowerShellUpdate(); + void this.checkForPowerShellUpdate(); } private async checkForPowerShellUpdate() { @@ -728,7 +734,7 @@ Type 'help' to get help. release); } catch (err) { // Best effort. This probably failed to fetch the data from GitHub. - this.log.writeWarning(err instanceof Error ? err.message : "unknown"); + this.logger.writeWarning(err instanceof Error ? err.message : "unknown"); } } @@ -790,9 +796,9 @@ Type 'help' to get help. this.setSessionStatus("Executing...", SessionStatus.Busy); } - private async setSessionFailure(message: string, ...additionalMessages: string[]) { + private setSessionFailure(message: string, ...additionalMessages: string[]): void { this.setSessionStatus("Initialization Error!", SessionStatus.Failed); - await this.log.writeAndShowError(message, ...additionalMessages); + void this.logger.writeAndShowError(message, ...additionalMessages); } private async changePowerShellDefaultVersion(exePath: IPowerShellExeDetails) { diff --git a/test/runTests.ts b/test/runTests.ts index e3cec43047..1e58705560 100644 --- a/test/runTests.ts +++ b/test/runTests.ts @@ -33,5 +33,4 @@ async function main() { } } -// eslint-disable-next-line @typescript-eslint/no-floating-promises -main(); +void main();