diff --git a/extensions/ql-vscode/src/commandRunner.ts b/extensions/ql-vscode/src/commandRunner.ts deleted file mode 100644 index 86b5cb971e1..00000000000 --- a/extensions/ql-vscode/src/commandRunner.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { commands, Disposable } from "vscode"; -import { - showAndLogExceptionWithTelemetry, - showAndLogWarningMessage, -} from "./helpers"; -import { extLogger } from "./common"; -import { asError, getErrorMessage, getErrorStack } from "./pure/helpers-pure"; -import { telemetryListener } from "./telemetry"; -import { redactableError } from "./pure/errors"; -import { UserCancellationException } from "./progress"; - -/** - * A task that handles command invocations from `commandRunner`. - * Arguments passed to the command handler are passed along, - * untouched to this `NoProgressTask` instance. - * - * @param args arguments passed to this task passed on from - * `commands.registerCommand`. - */ -export type NoProgressTask = (...args: any[]) => Promise; - -/** - * A generic wrapper for command registration. This wrapper adds uniform error handling for commands. - * - * In this variant of the command runner, no progress monitor is used. - * - * @param commandId The ID of the command to register. - * @param task The task to run. It is passed directly to `commands.registerCommand`. Any - * arguments to the command handler are passed on to the task. - */ -export function commandRunner( - commandId: string, - task: NoProgressTask, - outputLogger = extLogger, -): Disposable { - return commands.registerCommand(commandId, async (...args: any[]) => { - const startTime = Date.now(); - let error: Error | undefined; - - try { - return await task(...args); - } catch (e) { - error = asError(e); - const errorMessage = redactableError(error)`${ - getErrorMessage(e) || e - } (${commandId})`; - const errorStack = getErrorStack(e); - if (e instanceof UserCancellationException) { - // User has cancelled this action manually - if (e.silent) { - void outputLogger.log(errorMessage.fullMessage); - } else { - void showAndLogWarningMessage(errorMessage.fullMessage, { - outputLogger, - }); - } - } else { - // Include the full stack in the error log only. - const fullMessage = errorStack - ? `${errorMessage.fullMessage}\n${errorStack}` - : errorMessage.fullMessage; - void showAndLogExceptionWithTelemetry(errorMessage, { - outputLogger, - fullMessage, - extraTelemetryProperties: { - command: commandId, - }, - }); - } - return undefined; - } finally { - const executionTime = Date.now() - startTime; - telemetryListener?.sendCommandUsage(commandId, executionTime, error); - } - }); -} diff --git a/extensions/ql-vscode/src/common/vscode/commands.ts b/extensions/ql-vscode/src/common/vscode/commands.ts index fc8db2a782c..04aa02a5b44 100644 --- a/extensions/ql-vscode/src/common/vscode/commands.ts +++ b/extensions/ql-vscode/src/common/vscode/commands.ts @@ -1,7 +1,18 @@ -import { commands } from "vscode"; -import { commandRunner, NoProgressTask } from "../../commandRunner"; +import { commands, Disposable } from "vscode"; import { CommandFunction, CommandManager } from "../../packages/commands"; -import { OutputChannelLogger } from "../logging"; +import { extLogger, OutputChannelLogger } from "../logging"; +import { + asError, + getErrorMessage, + getErrorStack, +} from "../../pure/helpers-pure"; +import { redactableError } from "../../pure/errors"; +import { UserCancellationException } from "../../progress"; +import { + showAndLogExceptionWithTelemetry, + showAndLogWarningMessage, +} from "../../helpers"; +import { telemetryListener } from "../../telemetry"; /** * Create a command manager for VSCode, wrapping the commandRunner @@ -10,11 +21,65 @@ import { OutputChannelLogger } from "../logging"; export function createVSCodeCommandManager< Commands extends Record, >(outputLogger?: OutputChannelLogger): CommandManager { - return new CommandManager((commandId, task: NoProgressTask) => { - return commandRunner(commandId, task, outputLogger); + return new CommandManager((commandId, task) => { + return registerCommandWithErrorHandling(commandId, task, outputLogger); }, wrapExecuteCommand); } +/** + * A wrapper for command registration. This wrapper adds uniform error handling for commands. + * + * @param commandId The ID of the command to register. + * @param task The task to run. It is passed directly to `commands.registerCommand`. Any + * arguments to the command handler are passed on to the task. + */ +export function registerCommandWithErrorHandling( + commandId: string, + task: (...args: any[]) => Promise, + outputLogger = extLogger, +): Disposable { + return commands.registerCommand(commandId, async (...args: any[]) => { + const startTime = Date.now(); + let error: Error | undefined; + + try { + return await task(...args); + } catch (e) { + error = asError(e); + const errorMessage = redactableError(error)`${ + getErrorMessage(e) || e + } (${commandId})`; + const errorStack = getErrorStack(e); + if (e instanceof UserCancellationException) { + // User has cancelled this action manually + if (e.silent) { + void outputLogger.log(errorMessage.fullMessage); + } else { + void showAndLogWarningMessage(errorMessage.fullMessage, { + outputLogger, + }); + } + } else { + // Include the full stack in the error log only. + const fullMessage = errorStack + ? `${errorMessage.fullMessage}\n${errorStack}` + : errorMessage.fullMessage; + void showAndLogExceptionWithTelemetry(errorMessage, { + outputLogger, + fullMessage, + extraTelemetryProperties: { + command: commandId, + }, + }); + } + return undefined; + } finally { + const executionTime = Date.now() - startTime; + telemetryListener?.sendCommandUsage(commandId, executionTime, error); + } + }); +} + /** * wrapExecuteCommand wraps commands.executeCommand to satisfy that the * type is a Promise. Type script does not seem to be smart enough diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index 63ece980d15..fb973b9b776 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -87,7 +87,6 @@ import { QLTestAdapterFactory } from "./test-adapter"; import { TestUIService } from "./test-ui"; import { CompareView } from "./compare/compare-view"; import { initializeTelemetry } from "./telemetry"; -import { commandRunner } from "./commandRunner"; import { ProgressCallback, withProgress } from "./progress"; import { CodeQlStatusBarHandler } from "./status-bar"; import { getPackagingCommands } from "./packaging"; @@ -123,6 +122,7 @@ import { import { getAstCfgCommands } from "./ast-cfg-commands"; import { getQueryEditorCommands } from "./query-editor"; import { App } from "./common/app"; +import { registerCommandWithErrorHandling } from "./common/vscode/commands"; /** * extension.ts @@ -238,7 +238,9 @@ function registerErrorStubs( if (excludedCommands.indexOf(command) === -1) { // This is purposefully using `commandRunner` instead of the command manager because these // commands are untyped and registered pre-activation. - errorStubs.push(commandRunner(command, stubGenerator(command))); + errorStubs.push( + registerCommandWithErrorHandling(command, stubGenerator(command)), + ); } }); } @@ -341,7 +343,7 @@ export async function activate( ctx.subscriptions.push( // This is purposefully using `commandRunner` directly instead of the command manager // because this command is registered pre-activation. - commandRunner(checkForUpdatesCommand, () => + registerCommandWithErrorHandling(checkForUpdatesCommand, () => installOrUpdateThenTryActivate( ctx, app,