Rename and move `commandRunner`

The `commandRunner` name doesn't really make sense since it doesn't
"run" a command, but rather registers a command. This renames it
to `registerCommandWithErrorHandling` and moves it to the
`common/vscode` directory.
This commit is contained in:
Koen Vlaswinkel 2023-03-23 11:13:26 +01:00
Родитель aca0489fdc
Коммит aa268cfc5f
3 изменённых файлов: 75 добавлений и 84 удалений

Просмотреть файл

@ -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<any>;
/**
* 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);
}
});
}

Просмотреть файл

@ -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<string, CommandFunction>,
>(outputLogger?: OutputChannelLogger): CommandManager<Commands> {
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<any>,
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

Просмотреть файл

@ -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,