1673 lines
52 KiB
TypeScript
1673 lines
52 KiB
TypeScript
import "source-map-support/register";
|
|
import {
|
|
CancellationToken,
|
|
CancellationTokenSource,
|
|
commands,
|
|
Disposable,
|
|
env,
|
|
ExtensionContext,
|
|
extensions,
|
|
languages,
|
|
ProgressLocation,
|
|
ProgressOptions,
|
|
ProviderResult,
|
|
QuickPickItem,
|
|
Range,
|
|
Uri,
|
|
version as vscodeVersion,
|
|
window as Window,
|
|
window,
|
|
workspace,
|
|
} from "vscode";
|
|
import { LanguageClient } from "vscode-languageclient/node";
|
|
import { arch, platform } from "os";
|
|
import { ensureDir } from "fs-extra";
|
|
import { basename, join } from "path";
|
|
import { dirSync } from "tmp-promise";
|
|
import { testExplorerExtensionId, TestHub } from "vscode-test-adapter-api";
|
|
import { lt, parse } from "semver";
|
|
|
|
import { AstViewer } from "./astViewer";
|
|
import {
|
|
activate as archiveFilesystemProvider_activate,
|
|
zipArchiveScheme,
|
|
} from "./archive-filesystem-provider";
|
|
import QuickEvalCodeLensProvider from "./quickEvalCodeLensProvider";
|
|
import { CodeQLCliServer } from "./cli";
|
|
import {
|
|
CliConfigListener,
|
|
DistributionConfigListener,
|
|
isCanary,
|
|
joinOrderWarningThreshold,
|
|
MAX_QUERIES,
|
|
QueryHistoryConfigListener,
|
|
QueryServerConfigListener,
|
|
} from "./config";
|
|
import { install } from "./languageSupport";
|
|
import { DatabaseItem, DatabaseManager } from "./databases";
|
|
import { DatabaseUI } from "./databases-ui";
|
|
import {
|
|
TemplatePrintAstProvider,
|
|
TemplatePrintCfgProvider,
|
|
TemplateQueryDefinitionProvider,
|
|
TemplateQueryReferenceProvider,
|
|
} from "./contextual/templateProvider";
|
|
import {
|
|
DEFAULT_DISTRIBUTION_VERSION_RANGE,
|
|
DistributionKind,
|
|
DistributionManager,
|
|
DistributionUpdateCheckResultKind,
|
|
FindDistributionResult,
|
|
FindDistributionResultKind,
|
|
GithubApiError,
|
|
GithubRateLimitedError,
|
|
} from "./distribution";
|
|
import {
|
|
findLanguage,
|
|
showAndLogErrorMessage,
|
|
showAndLogExceptionWithTelemetry,
|
|
showAndLogInformationMessage,
|
|
showAndLogWarningMessage,
|
|
showBinaryChoiceDialog,
|
|
showInformationMessageWithAction,
|
|
tmpDir,
|
|
tmpDirDisposal,
|
|
} from "./helpers";
|
|
import { asError, assertNever, getErrorMessage } from "./pure/helpers-pure";
|
|
import { spawnIdeServer } from "./ide-server";
|
|
import { ResultsView } from "./interface";
|
|
import { WebviewReveal } from "./interface-utils";
|
|
import {
|
|
extLogger,
|
|
ideServerLogger,
|
|
ProgressReporter,
|
|
queryServerLogger,
|
|
} from "./common";
|
|
import { QueryHistoryManager } from "./query-history/query-history-manager";
|
|
import { CompletedLocalQueryInfo, LocalQueryInfo } from "./query-results";
|
|
import { QueryServerClient as LegacyQueryServerClient } from "./legacy-query-server/queryserver-client";
|
|
import { QueryServerClient } from "./query-server/queryserver-client";
|
|
import { displayQuickQuery } from "./quick-query";
|
|
import { QLTestAdapterFactory } from "./test-adapter";
|
|
import { TestUIService } from "./test-ui";
|
|
import { CompareView } from "./compare/compare-view";
|
|
import { gatherQlFiles } from "./pure/files";
|
|
import { initializeTelemetry } from "./telemetry";
|
|
import {
|
|
commandRunner,
|
|
commandRunnerWithProgress,
|
|
ProgressCallback,
|
|
ProgressUpdate,
|
|
withProgress,
|
|
} from "./commandRunner";
|
|
import { CodeQlStatusBarHandler } from "./status-bar";
|
|
|
|
import { RemoteQueriesManager } from "./remote-queries/remote-queries-manager";
|
|
import { URLSearchParams } from "url";
|
|
import {
|
|
handleDownloadPacks,
|
|
handleInstallPackDependencies,
|
|
} from "./packaging";
|
|
import { HistoryItemLabelProvider } from "./query-history/history-item-label-provider";
|
|
import {
|
|
exportRemoteQueryResults,
|
|
exportSelectedRemoteQueryResults,
|
|
exportVariantAnalysisResults,
|
|
} from "./remote-queries/export-results";
|
|
import { EvalLogViewer } from "./eval-log-viewer";
|
|
import { SummaryLanguageSupport } from "./log-insights/summary-language-support";
|
|
import { JoinOrderScannerProvider } from "./log-insights/join-order";
|
|
import { LogScannerService } from "./log-insights/log-scanner-service";
|
|
import { createInitialQueryInfo } from "./run-queries-shared";
|
|
import { LegacyQueryRunner } from "./legacy-query-server/legacyRunner";
|
|
import { NewQueryRunner } from "./query-server/query-runner";
|
|
import { QueryRunner } from "./queryRunner";
|
|
import { VariantAnalysisView } from "./remote-queries/variant-analysis-view";
|
|
import { VariantAnalysisViewSerializer } from "./remote-queries/variant-analysis-view-serializer";
|
|
import {
|
|
VariantAnalysis,
|
|
VariantAnalysisScannedRepository,
|
|
} from "./remote-queries/shared/variant-analysis";
|
|
import { VariantAnalysisManager } from "./remote-queries/variant-analysis-manager";
|
|
import { createVariantAnalysisContentProvider } from "./remote-queries/variant-analysis-content-provider";
|
|
import { VSCodeMockGitHubApiServer } from "./mocks/vscode-mock-gh-api-server";
|
|
import { VariantAnalysisResultsManager } from "./remote-queries/variant-analysis-results-manager";
|
|
import { ExtensionApp } from "./common/vscode/vscode-app";
|
|
import { RepositoriesFilterSortStateWithIds } from "./pure/variant-analysis-filter-sort";
|
|
import { DbModule } from "./databases/db-module";
|
|
import { redactableError } from "./pure/errors";
|
|
|
|
/**
|
|
* extension.ts
|
|
* ------------
|
|
*
|
|
* A vscode extension for CodeQL query development.
|
|
*/
|
|
|
|
/**
|
|
* Holds when we have proceeded past the initial phase of extension activation in which
|
|
* we are trying to ensure that a valid CodeQL distribution exists, and we're actually setting
|
|
* up the bulk of the extension.
|
|
*/
|
|
let beganMainExtensionActivation = false;
|
|
|
|
/**
|
|
* A list of vscode-registered-command disposables that contain
|
|
* temporary stub handlers for commands that exist package.json (hence
|
|
* are already connected to onscreen ui elements) but which will not
|
|
* have any useful effect if we haven't located a CodeQL distribution.
|
|
*/
|
|
const errorStubs: Disposable[] = [];
|
|
|
|
/**
|
|
* Holds when we are installing or checking for updates to the distribution.
|
|
*/
|
|
let isInstallingOrUpdatingDistribution = false;
|
|
|
|
const extensionId = "GitHub.vscode-codeql";
|
|
const extension = extensions.getExtension(extensionId);
|
|
|
|
/**
|
|
* If the user tries to execute vscode commands after extension activation is failed, give
|
|
* a sensible error message.
|
|
*
|
|
* @param excludedCommands List of commands for which we should not register error stubs.
|
|
*/
|
|
function registerErrorStubs(
|
|
excludedCommands: string[],
|
|
stubGenerator: (command: string) => () => Promise<void>,
|
|
): void {
|
|
// Remove existing stubs
|
|
errorStubs.forEach((stub) => stub.dispose());
|
|
|
|
if (extension === undefined) {
|
|
throw new Error(`Can't find extension ${extensionId}`);
|
|
}
|
|
|
|
const stubbedCommands: string[] =
|
|
extension.packageJSON.contributes.commands.map(
|
|
(entry: { command: string }) => entry.command,
|
|
);
|
|
|
|
stubbedCommands.forEach((command) => {
|
|
if (excludedCommands.indexOf(command) === -1) {
|
|
errorStubs.push(commandRunner(command, stubGenerator(command)));
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* The publicly available interface for this extension. This is to
|
|
* be used in our tests.
|
|
*/
|
|
export interface CodeQLExtensionInterface {
|
|
readonly ctx: ExtensionContext;
|
|
readonly cliServer: CodeQLCliServer;
|
|
readonly qs: QueryRunner;
|
|
readonly distributionManager: DistributionManager;
|
|
readonly databaseManager: DatabaseManager;
|
|
readonly databaseUI: DatabaseUI;
|
|
readonly variantAnalysisManager: VariantAnalysisManager;
|
|
readonly dispose: () => void;
|
|
}
|
|
|
|
// This is the minimum version of vscode that we _want_ to support. We want to update the language server library, but that
|
|
// requires 1.67 or later. If we change the minimum version in the package.json, then anyone on an older version of vscode
|
|
// silently be unable to upgrade. So, the solution is to first bump the minimum version here and release. Then
|
|
// bump the version in the package.json and release again. This way, anyone on an older version of vscode will get a warning
|
|
// before silently being refused to upgrade.
|
|
const MIN_VERSION = "1.67.0";
|
|
|
|
/**
|
|
* Returns the CodeQLExtensionInterface, or an empty object if the interface is not
|
|
* available after activation is complete. This will happen if there is no cli
|
|
* installed when the extension starts. Downloading and installing the cli
|
|
* will happen at a later time.
|
|
*
|
|
* @param ctx The extension context
|
|
*
|
|
* @returns CodeQLExtensionInterface
|
|
*/
|
|
export async function activate(
|
|
ctx: ExtensionContext,
|
|
): Promise<CodeQLExtensionInterface | Record<string, never>> {
|
|
void extLogger.log(`Starting ${extensionId} extension`);
|
|
if (extension === undefined) {
|
|
throw new Error(`Can't find extension ${extensionId}`);
|
|
}
|
|
|
|
const distributionConfigListener = new DistributionConfigListener();
|
|
await initializeLogging(ctx);
|
|
await initializeTelemetry(extension, ctx);
|
|
install();
|
|
|
|
const codelensProvider = new QuickEvalCodeLensProvider();
|
|
languages.registerCodeLensProvider(
|
|
{ scheme: "file", language: "ql" },
|
|
codelensProvider,
|
|
);
|
|
|
|
ctx.subscriptions.push(distributionConfigListener);
|
|
const codeQlVersionRange = DEFAULT_DISTRIBUTION_VERSION_RANGE;
|
|
const distributionManager = new DistributionManager(
|
|
distributionConfigListener,
|
|
codeQlVersionRange,
|
|
ctx,
|
|
);
|
|
|
|
const shouldUpdateOnNextActivationKey = "shouldUpdateOnNextActivation";
|
|
|
|
registerErrorStubs([checkForUpdatesCommand], (command) => async () => {
|
|
void showAndLogErrorMessage(
|
|
`Can't execute ${command}: waiting to finish loading CodeQL CLI.`,
|
|
);
|
|
});
|
|
|
|
// Checking the vscode version should not block extension activation.
|
|
void assertVSCodeVersionGreaterThan(MIN_VERSION, ctx);
|
|
|
|
interface DistributionUpdateConfig {
|
|
isUserInitiated: boolean;
|
|
shouldDisplayMessageWhenNoUpdates: boolean;
|
|
allowAutoUpdating: boolean;
|
|
}
|
|
|
|
async function installOrUpdateDistributionWithProgressTitle(
|
|
progressTitle: string,
|
|
config: DistributionUpdateConfig,
|
|
): Promise<void> {
|
|
const minSecondsSinceLastUpdateCheck = config.isUserInitiated ? 0 : 86400;
|
|
const noUpdatesLoggingFunc = config.shouldDisplayMessageWhenNoUpdates
|
|
? showAndLogInformationMessage
|
|
: async (message: string) => void extLogger.log(message);
|
|
const result =
|
|
await distributionManager.checkForUpdatesToExtensionManagedDistribution(
|
|
minSecondsSinceLastUpdateCheck,
|
|
);
|
|
|
|
// We do want to auto update if there is no distribution at all
|
|
const allowAutoUpdating =
|
|
config.allowAutoUpdating ||
|
|
!(await distributionManager.hasDistribution());
|
|
|
|
switch (result.kind) {
|
|
case DistributionUpdateCheckResultKind.AlreadyCheckedRecentlyResult:
|
|
void extLogger.log(
|
|
"Didn't perform CodeQL CLI update check since a check was already performed within the previous " +
|
|
`${minSecondsSinceLastUpdateCheck} seconds.`,
|
|
);
|
|
break;
|
|
case DistributionUpdateCheckResultKind.AlreadyUpToDate:
|
|
await noUpdatesLoggingFunc("CodeQL CLI already up to date.");
|
|
break;
|
|
case DistributionUpdateCheckResultKind.InvalidLocation:
|
|
await noUpdatesLoggingFunc(
|
|
"CodeQL CLI is installed externally so could not be updated.",
|
|
);
|
|
break;
|
|
case DistributionUpdateCheckResultKind.UpdateAvailable:
|
|
if (beganMainExtensionActivation || !allowAutoUpdating) {
|
|
const updateAvailableMessage =
|
|
`Version "${result.updatedRelease.name}" of the CodeQL CLI is now available. ` +
|
|
"Do you wish to upgrade?";
|
|
await ctx.globalState.update(shouldUpdateOnNextActivationKey, true);
|
|
if (
|
|
await showInformationMessageWithAction(
|
|
updateAvailableMessage,
|
|
"Restart and Upgrade",
|
|
)
|
|
) {
|
|
await commands.executeCommand("workbench.action.reloadWindow");
|
|
}
|
|
} else {
|
|
const progressOptions: ProgressOptions = {
|
|
title: progressTitle,
|
|
location: ProgressLocation.Notification,
|
|
};
|
|
|
|
await withProgress(progressOptions, (progress) =>
|
|
distributionManager.installExtensionManagedDistributionRelease(
|
|
result.updatedRelease,
|
|
progress,
|
|
),
|
|
);
|
|
|
|
await ctx.globalState.update(shouldUpdateOnNextActivationKey, false);
|
|
void showAndLogInformationMessage(
|
|
`CodeQL CLI updated to version "${result.updatedRelease.name}".`,
|
|
);
|
|
}
|
|
break;
|
|
default:
|
|
assertNever(result);
|
|
}
|
|
}
|
|
|
|
async function installOrUpdateDistribution(
|
|
config: DistributionUpdateConfig,
|
|
): Promise<void> {
|
|
if (isInstallingOrUpdatingDistribution) {
|
|
throw new Error("Already installing or updating CodeQL CLI");
|
|
}
|
|
isInstallingOrUpdatingDistribution = true;
|
|
const codeQlInstalled =
|
|
(await distributionManager.getCodeQlPathWithoutVersionCheck()) !==
|
|
undefined;
|
|
const willUpdateCodeQl = ctx.globalState.get(
|
|
shouldUpdateOnNextActivationKey,
|
|
);
|
|
const messageText = willUpdateCodeQl
|
|
? "Updating CodeQL CLI"
|
|
: codeQlInstalled
|
|
? "Checking for updates to CodeQL CLI"
|
|
: "Installing CodeQL CLI";
|
|
|
|
try {
|
|
await installOrUpdateDistributionWithProgressTitle(messageText, config);
|
|
} catch (e) {
|
|
// Don't rethrow the exception, because if the config is changed, we want to be able to retry installing
|
|
// or updating the distribution.
|
|
const alertFunction =
|
|
codeQlInstalled && !config.isUserInitiated
|
|
? showAndLogWarningMessage
|
|
: showAndLogErrorMessage;
|
|
const taskDescription = `${
|
|
willUpdateCodeQl
|
|
? "update"
|
|
: codeQlInstalled
|
|
? "check for updates to"
|
|
: "install"
|
|
} CodeQL CLI`;
|
|
|
|
if (e instanceof GithubRateLimitedError) {
|
|
void alertFunction(
|
|
`Rate limited while trying to ${taskDescription}. Please try again after ` +
|
|
`your rate limit window resets at ${e.rateLimitResetDate.toLocaleString(
|
|
env.language,
|
|
)}.`,
|
|
);
|
|
} else if (e instanceof GithubApiError) {
|
|
void alertFunction(
|
|
`Encountered GitHub API error while trying to ${taskDescription}. ${e}`,
|
|
);
|
|
}
|
|
void alertFunction(`Unable to ${taskDescription}. ${e}`);
|
|
} finally {
|
|
isInstallingOrUpdatingDistribution = false;
|
|
}
|
|
}
|
|
|
|
async function getDistributionDisplayingDistributionWarnings(): Promise<FindDistributionResult> {
|
|
const result = await distributionManager.getDistribution();
|
|
switch (result.kind) {
|
|
case FindDistributionResultKind.CompatibleDistribution:
|
|
void extLogger.log(
|
|
`Found compatible version of CodeQL CLI (version ${result.version.raw})`,
|
|
);
|
|
break;
|
|
case FindDistributionResultKind.IncompatibleDistribution: {
|
|
const fixGuidanceMessage = (() => {
|
|
switch (result.distribution.kind) {
|
|
case DistributionKind.ExtensionManaged:
|
|
return 'Please update the CodeQL CLI by running the "CodeQL: Check for CLI Updates" command.';
|
|
case DistributionKind.CustomPathConfig:
|
|
return `Please update the \"CodeQL CLI Executable Path\" setting to point to a CLI in the version range ${codeQlVersionRange}.`;
|
|
case DistributionKind.PathEnvironmentVariable:
|
|
return (
|
|
`Please update the CodeQL CLI on your PATH to a version compatible with ${codeQlVersionRange}, or ` +
|
|
`set the \"CodeQL CLI Executable Path\" setting to the path of a CLI version compatible with ${codeQlVersionRange}.`
|
|
);
|
|
}
|
|
})();
|
|
|
|
void showAndLogWarningMessage(
|
|
`The current version of the CodeQL CLI (${result.version.raw}) ` +
|
|
`is incompatible with this extension. ${fixGuidanceMessage}`,
|
|
);
|
|
break;
|
|
}
|
|
case FindDistributionResultKind.UnknownCompatibilityDistribution:
|
|
void showAndLogWarningMessage(
|
|
"Compatibility with the configured CodeQL CLI could not be determined. " +
|
|
"You may experience problems using the extension.",
|
|
);
|
|
break;
|
|
case FindDistributionResultKind.NoDistribution:
|
|
void showAndLogErrorMessage("The CodeQL CLI could not be found.");
|
|
break;
|
|
default:
|
|
assertNever(result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
async function installOrUpdateThenTryActivate(
|
|
config: DistributionUpdateConfig,
|
|
): Promise<CodeQLExtensionInterface | Record<string, never>> {
|
|
await installOrUpdateDistribution(config);
|
|
|
|
// Display the warnings even if the extension has already activated.
|
|
const distributionResult =
|
|
await getDistributionDisplayingDistributionWarnings();
|
|
let extensionInterface: CodeQLExtensionInterface | Record<string, never> =
|
|
{};
|
|
if (
|
|
!beganMainExtensionActivation &&
|
|
distributionResult.kind !== FindDistributionResultKind.NoDistribution
|
|
) {
|
|
extensionInterface = await activateWithInstalledDistribution(
|
|
ctx,
|
|
distributionManager,
|
|
distributionConfigListener,
|
|
);
|
|
} else if (
|
|
distributionResult.kind === FindDistributionResultKind.NoDistribution
|
|
) {
|
|
registerErrorStubs([checkForUpdatesCommand], (command) => async () => {
|
|
const installActionName = "Install CodeQL CLI";
|
|
const chosenAction = await void showAndLogErrorMessage(
|
|
`Can't execute ${command}: missing CodeQL CLI.`,
|
|
{
|
|
items: [installActionName],
|
|
},
|
|
);
|
|
if (chosenAction === installActionName) {
|
|
await installOrUpdateThenTryActivate({
|
|
isUserInitiated: true,
|
|
shouldDisplayMessageWhenNoUpdates: false,
|
|
allowAutoUpdating: true,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
return extensionInterface;
|
|
}
|
|
|
|
ctx.subscriptions.push(
|
|
distributionConfigListener.onDidChangeConfiguration(() =>
|
|
installOrUpdateThenTryActivate({
|
|
isUserInitiated: true,
|
|
shouldDisplayMessageWhenNoUpdates: false,
|
|
allowAutoUpdating: true,
|
|
}),
|
|
),
|
|
);
|
|
ctx.subscriptions.push(
|
|
commandRunner(checkForUpdatesCommand, () =>
|
|
installOrUpdateThenTryActivate({
|
|
isUserInitiated: true,
|
|
shouldDisplayMessageWhenNoUpdates: true,
|
|
allowAutoUpdating: true,
|
|
}),
|
|
),
|
|
);
|
|
|
|
const variantAnalysisViewSerializer = new VariantAnalysisViewSerializer(ctx);
|
|
Window.registerWebviewPanelSerializer(
|
|
VariantAnalysisView.viewType,
|
|
variantAnalysisViewSerializer,
|
|
);
|
|
|
|
const codeQlExtension = await installOrUpdateThenTryActivate({
|
|
isUserInitiated: !!ctx.globalState.get(shouldUpdateOnNextActivationKey),
|
|
shouldDisplayMessageWhenNoUpdates: false,
|
|
|
|
// only auto update on startup if the user has previously requested an update
|
|
// otherwise, ask user to accept the update
|
|
allowAutoUpdating: !!ctx.globalState.get(shouldUpdateOnNextActivationKey),
|
|
});
|
|
|
|
variantAnalysisViewSerializer.onExtensionLoaded(
|
|
codeQlExtension.variantAnalysisManager,
|
|
);
|
|
|
|
return codeQlExtension;
|
|
}
|
|
|
|
const PACK_GLOBS = [
|
|
"**/codeql-pack.yml",
|
|
"**/qlpack.yml",
|
|
"**/queries.xml",
|
|
"**/codeql-pack.lock.yml",
|
|
"**/qlpack.lock.yml",
|
|
".codeqlmanifest.json",
|
|
"codeql-workspace.yml",
|
|
];
|
|
|
|
async function activateWithInstalledDistribution(
|
|
ctx: ExtensionContext,
|
|
distributionManager: DistributionManager,
|
|
distributionConfigListener: DistributionConfigListener,
|
|
): Promise<CodeQLExtensionInterface> {
|
|
beganMainExtensionActivation = true;
|
|
// Remove any error stubs command handlers left over from first part
|
|
// of activation.
|
|
errorStubs.forEach((stub) => stub.dispose());
|
|
|
|
const app = new ExtensionApp(ctx);
|
|
|
|
void extLogger.log("Initializing configuration listener...");
|
|
const qlConfigurationListener =
|
|
await QueryServerConfigListener.createQueryServerConfigListener(
|
|
distributionManager,
|
|
);
|
|
ctx.subscriptions.push(qlConfigurationListener);
|
|
|
|
void extLogger.log("Initializing CodeQL cli server...");
|
|
const cliServer = new CodeQLCliServer(
|
|
app,
|
|
distributionManager,
|
|
new CliConfigListener(),
|
|
extLogger,
|
|
);
|
|
ctx.subscriptions.push(cliServer);
|
|
|
|
const statusBar = new CodeQlStatusBarHandler(
|
|
cliServer,
|
|
distributionConfigListener,
|
|
);
|
|
ctx.subscriptions.push(statusBar);
|
|
|
|
void extLogger.log("Initializing query server client.");
|
|
const qs = await createQueryServer(qlConfigurationListener, cliServer, ctx);
|
|
|
|
for (const glob of PACK_GLOBS) {
|
|
const fsWatcher = workspace.createFileSystemWatcher(glob);
|
|
ctx.subscriptions.push(fsWatcher);
|
|
fsWatcher.onDidChange(async (_uri) => {
|
|
await qs.clearPackCache();
|
|
});
|
|
}
|
|
|
|
void extLogger.log("Initializing database manager.");
|
|
const dbm = new DatabaseManager(ctx, qs, cliServer, extLogger);
|
|
|
|
// Let this run async.
|
|
void dbm.loadPersistedState();
|
|
|
|
ctx.subscriptions.push(dbm);
|
|
void extLogger.log("Initializing database panel.");
|
|
const databaseUI = new DatabaseUI(
|
|
app,
|
|
dbm,
|
|
qs,
|
|
getContextStoragePath(ctx),
|
|
ctx.extensionPath,
|
|
);
|
|
databaseUI.init();
|
|
ctx.subscriptions.push(databaseUI);
|
|
|
|
void extLogger.log("Initializing evaluator log viewer.");
|
|
const evalLogViewer = new EvalLogViewer();
|
|
ctx.subscriptions.push(evalLogViewer);
|
|
|
|
void extLogger.log("Initializing query history manager.");
|
|
const queryHistoryConfigurationListener = new QueryHistoryConfigListener();
|
|
ctx.subscriptions.push(queryHistoryConfigurationListener);
|
|
const showResults = async (item: CompletedLocalQueryInfo) =>
|
|
showResultsForCompletedQuery(item, WebviewReveal.Forced);
|
|
const queryStorageDir = join(ctx.globalStorageUri.fsPath, "queries");
|
|
await ensureDir(queryStorageDir);
|
|
const labelProvider = new HistoryItemLabelProvider(
|
|
queryHistoryConfigurationListener,
|
|
);
|
|
|
|
void extLogger.log("Initializing results panel interface.");
|
|
const localQueryResultsView = new ResultsView(
|
|
ctx,
|
|
dbm,
|
|
cliServer,
|
|
queryServerLogger,
|
|
labelProvider,
|
|
);
|
|
ctx.subscriptions.push(localQueryResultsView);
|
|
|
|
void extLogger.log("Initializing variant analysis manager.");
|
|
|
|
const dbModule = await DbModule.initialize(app);
|
|
|
|
const variantAnalysisStorageDir = join(
|
|
ctx.globalStorageUri.fsPath,
|
|
"variant-analyses",
|
|
);
|
|
await ensureDir(variantAnalysisStorageDir);
|
|
const variantAnalysisResultsManager = new VariantAnalysisResultsManager(
|
|
cliServer,
|
|
extLogger,
|
|
);
|
|
|
|
const variantAnalysisManager = new VariantAnalysisManager(
|
|
ctx,
|
|
app,
|
|
cliServer,
|
|
variantAnalysisStorageDir,
|
|
variantAnalysisResultsManager,
|
|
dbModule?.dbManager, // the dbModule is only needed when variantAnalysisReposPanel is enabled
|
|
);
|
|
ctx.subscriptions.push(variantAnalysisManager);
|
|
ctx.subscriptions.push(variantAnalysisResultsManager);
|
|
ctx.subscriptions.push(
|
|
workspace.registerTextDocumentContentProvider(
|
|
"codeql-variant-analysis",
|
|
createVariantAnalysisContentProvider(variantAnalysisManager),
|
|
),
|
|
);
|
|
|
|
void extLogger.log("Initializing remote queries manager.");
|
|
const rqm = new RemoteQueriesManager(
|
|
ctx,
|
|
app,
|
|
cliServer,
|
|
queryStorageDir,
|
|
extLogger,
|
|
);
|
|
ctx.subscriptions.push(rqm);
|
|
|
|
void extLogger.log("Initializing query history.");
|
|
const qhm = new QueryHistoryManager(
|
|
app,
|
|
qs,
|
|
dbm,
|
|
localQueryResultsView,
|
|
rqm,
|
|
variantAnalysisManager,
|
|
evalLogViewer,
|
|
queryStorageDir,
|
|
ctx,
|
|
queryHistoryConfigurationListener,
|
|
labelProvider,
|
|
async (from: CompletedLocalQueryInfo, to: CompletedLocalQueryInfo) =>
|
|
showResultsForComparison(from, to),
|
|
);
|
|
|
|
ctx.subscriptions.push(qhm);
|
|
|
|
void extLogger.log("Initializing evaluation log scanners.");
|
|
const logScannerService = new LogScannerService(qhm);
|
|
ctx.subscriptions.push(logScannerService);
|
|
ctx.subscriptions.push(
|
|
logScannerService.scanners.registerLogScannerProvider(
|
|
new JoinOrderScannerProvider(() => joinOrderWarningThreshold()),
|
|
),
|
|
);
|
|
|
|
void extLogger.log("Initializing compare view.");
|
|
const compareView = new CompareView(
|
|
ctx,
|
|
dbm,
|
|
cliServer,
|
|
queryServerLogger,
|
|
labelProvider,
|
|
showResults,
|
|
);
|
|
ctx.subscriptions.push(compareView);
|
|
|
|
void extLogger.log("Initializing source archive filesystem provider.");
|
|
archiveFilesystemProvider_activate(ctx);
|
|
|
|
async function showResultsForComparison(
|
|
from: CompletedLocalQueryInfo,
|
|
to: CompletedLocalQueryInfo,
|
|
): Promise<void> {
|
|
try {
|
|
await compareView.showResults(from, to);
|
|
} catch (e) {
|
|
void showAndLogExceptionWithTelemetry(
|
|
redactableError(asError(e))`Failed to show results: ${getErrorMessage(
|
|
e,
|
|
)}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
async function showResultsForCompletedQuery(
|
|
query: CompletedLocalQueryInfo,
|
|
forceReveal: WebviewReveal,
|
|
): Promise<void> {
|
|
await localQueryResultsView.showResults(query, forceReveal, false);
|
|
}
|
|
|
|
async function compileAndRunQuery(
|
|
quickEval: boolean,
|
|
selectedQuery: Uri | undefined,
|
|
progress: ProgressCallback,
|
|
token: CancellationToken,
|
|
databaseItem: DatabaseItem | undefined,
|
|
range?: Range,
|
|
): Promise<void> {
|
|
if (qs !== undefined) {
|
|
// If no databaseItem is specified, use the database currently selected in the Databases UI
|
|
databaseItem =
|
|
databaseItem || (await databaseUI.getDatabaseItem(progress, token));
|
|
if (databaseItem === undefined) {
|
|
throw new Error("Can't run query without a selected database");
|
|
}
|
|
const databaseInfo = {
|
|
name: databaseItem.name,
|
|
databaseUri: databaseItem.databaseUri.toString(),
|
|
};
|
|
|
|
// handle cancellation from the history view.
|
|
const source = new CancellationTokenSource();
|
|
token.onCancellationRequested(() => source.cancel());
|
|
|
|
const initialInfo = await createInitialQueryInfo(
|
|
selectedQuery,
|
|
databaseInfo,
|
|
quickEval,
|
|
range,
|
|
);
|
|
const item = new LocalQueryInfo(initialInfo, source);
|
|
qhm.addQuery(item);
|
|
try {
|
|
const completedQueryInfo = await qs.compileAndRunQueryAgainstDatabase(
|
|
databaseItem,
|
|
initialInfo,
|
|
queryStorageDir,
|
|
progress,
|
|
source.token,
|
|
undefined,
|
|
item,
|
|
);
|
|
qhm.completeQuery(item, completedQueryInfo);
|
|
await showResultsForCompletedQuery(
|
|
item as CompletedLocalQueryInfo,
|
|
WebviewReveal.Forced,
|
|
);
|
|
// Note we must update the query history view after showing results as the
|
|
// display and sorting might depend on the number of results
|
|
} catch (e) {
|
|
const err = asError(e);
|
|
err.message = `Error running query: ${err.message}`;
|
|
item.failureReason = err.message;
|
|
throw e;
|
|
} finally {
|
|
await qhm.refreshTreeView();
|
|
source.dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
const qhelpTmpDir = dirSync({
|
|
prefix: "qhelp_",
|
|
keep: false,
|
|
unsafeCleanup: true,
|
|
});
|
|
ctx.subscriptions.push({ dispose: qhelpTmpDir.removeCallback });
|
|
|
|
async function previewQueryHelp(selectedQuery: Uri): Promise<void> {
|
|
// selectedQuery is unpopulated when executing through the command palette
|
|
const pathToQhelp = selectedQuery
|
|
? selectedQuery.fsPath
|
|
: window.activeTextEditor?.document.uri.fsPath;
|
|
if (pathToQhelp) {
|
|
// Create temporary directory
|
|
const relativePathToMd = `${basename(pathToQhelp, ".qhelp")}.md`;
|
|
const absolutePathToMd = join(qhelpTmpDir.name, relativePathToMd);
|
|
const uri = Uri.file(absolutePathToMd);
|
|
try {
|
|
await cliServer.generateQueryHelp(pathToQhelp, absolutePathToMd);
|
|
await commands.executeCommand("markdown.showPreviewToSide", uri);
|
|
} catch (e) {
|
|
const errorMessage = getErrorMessage(e).includes(
|
|
"Generating qhelp in markdown",
|
|
)
|
|
? redactableError`Could not generate markdown from ${pathToQhelp}: Bad formatting in .qhelp file.`
|
|
: redactableError`Could not open a preview of the generated file (${absolutePathToMd}).`;
|
|
void showAndLogExceptionWithTelemetry(errorMessage, {
|
|
fullMessage: `${errorMessage}\n${getErrorMessage(e)}`,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
async function openReferencedFile(selectedQuery: Uri): Promise<void> {
|
|
// If no file is selected, the path of the file in the editor is selected
|
|
const path =
|
|
selectedQuery?.fsPath || window.activeTextEditor?.document.uri.fsPath;
|
|
if (qs !== undefined && path) {
|
|
const resolved = await cliServer.resolveQlref(path);
|
|
const uri = Uri.file(resolved.resolvedPath);
|
|
await window.showTextDocument(uri, { preview: false });
|
|
}
|
|
}
|
|
|
|
ctx.subscriptions.push(tmpDirDisposal);
|
|
|
|
void extLogger.log("Initializing CodeQL language server.");
|
|
const client = new LanguageClient(
|
|
"CodeQL Language Server",
|
|
() => spawnIdeServer(qlConfigurationListener),
|
|
{
|
|
documentSelector: [
|
|
{ language: "ql", scheme: "file" },
|
|
{ language: "yaml", scheme: "file", pattern: "**/qlpack.yml" },
|
|
{ language: "yaml", scheme: "file", pattern: "**/codeql-pack.yml" },
|
|
],
|
|
synchronize: {
|
|
configurationSection: "codeQL",
|
|
},
|
|
// Ensure that language server exceptions are logged to the same channel as its output.
|
|
outputChannel: ideServerLogger.outputChannel,
|
|
},
|
|
true,
|
|
);
|
|
|
|
void extLogger.log("Initializing QLTest interface.");
|
|
const testExplorerExtension = extensions.getExtension<TestHub>(
|
|
testExplorerExtensionId,
|
|
);
|
|
if (testExplorerExtension) {
|
|
const testHub = testExplorerExtension.exports;
|
|
const testAdapterFactory = new QLTestAdapterFactory(
|
|
testHub,
|
|
cliServer,
|
|
dbm,
|
|
);
|
|
ctx.subscriptions.push(testAdapterFactory);
|
|
|
|
const testUIService = new TestUIService(testHub);
|
|
ctx.subscriptions.push(testUIService);
|
|
}
|
|
|
|
void extLogger.log("Registering top-level command palette commands.");
|
|
ctx.subscriptions.push(
|
|
commandRunnerWithProgress(
|
|
"codeQL.runQuery",
|
|
async (
|
|
progress: ProgressCallback,
|
|
token: CancellationToken,
|
|
uri: Uri | undefined,
|
|
) => await compileAndRunQuery(false, uri, progress, token, undefined),
|
|
{
|
|
title: "Running query",
|
|
cancellable: true,
|
|
},
|
|
|
|
// Open the query server logger on error since that's usually where the interesting errors appear.
|
|
queryServerLogger,
|
|
),
|
|
);
|
|
interface DatabaseQuickPickItem extends QuickPickItem {
|
|
databaseItem: DatabaseItem;
|
|
}
|
|
ctx.subscriptions.push(
|
|
commandRunnerWithProgress(
|
|
"codeQL.runQueryOnMultipleDatabases",
|
|
async (
|
|
progress: ProgressCallback,
|
|
token: CancellationToken,
|
|
uri: Uri | undefined,
|
|
) => {
|
|
let filteredDBs = dbm.databaseItems;
|
|
if (filteredDBs.length === 0) {
|
|
void showAndLogErrorMessage(
|
|
"No databases found. Please add a suitable database to your workspace.",
|
|
);
|
|
return;
|
|
}
|
|
// If possible, only show databases with the right language (otherwise show all databases).
|
|
const queryLanguage = await findLanguage(cliServer, uri);
|
|
if (queryLanguage) {
|
|
filteredDBs = dbm.databaseItems.filter(
|
|
(db) => db.language === queryLanguage,
|
|
);
|
|
if (filteredDBs.length === 0) {
|
|
void showAndLogErrorMessage(
|
|
`No databases found for language ${queryLanguage}. Please add a suitable database to your workspace.`,
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
const quickPickItems = filteredDBs.map<DatabaseQuickPickItem>(
|
|
(dbItem) => ({
|
|
databaseItem: dbItem,
|
|
label: dbItem.name,
|
|
description: dbItem.language,
|
|
}),
|
|
);
|
|
/**
|
|
* Databases that were selected in the quick pick menu.
|
|
*/
|
|
const quickpick = await window.showQuickPick<DatabaseQuickPickItem>(
|
|
quickPickItems,
|
|
{ canPickMany: true, ignoreFocusOut: true },
|
|
);
|
|
if (quickpick !== undefined) {
|
|
// Collect all skipped databases and display them at the end (instead of popping up individual errors)
|
|
const skippedDatabases = [];
|
|
const errors = [];
|
|
for (const item of quickpick) {
|
|
try {
|
|
await compileAndRunQuery(
|
|
false,
|
|
uri,
|
|
progress,
|
|
token,
|
|
item.databaseItem,
|
|
);
|
|
} catch (e) {
|
|
skippedDatabases.push(item.label);
|
|
errors.push(getErrorMessage(e));
|
|
}
|
|
}
|
|
if (skippedDatabases.length > 0) {
|
|
void extLogger.log(`Errors:\n${errors.join("\n")}`);
|
|
void showAndLogWarningMessage(
|
|
`The following databases were skipped:\n${skippedDatabases.join(
|
|
"\n",
|
|
)}.\nFor details about the errors, see the logs.`,
|
|
);
|
|
}
|
|
} else {
|
|
void showAndLogErrorMessage("No databases selected.");
|
|
}
|
|
},
|
|
{
|
|
title: "Running query on selected databases",
|
|
cancellable: true,
|
|
},
|
|
),
|
|
);
|
|
ctx.subscriptions.push(
|
|
commandRunnerWithProgress(
|
|
"codeQL.runQueries",
|
|
async (
|
|
progress: ProgressCallback,
|
|
token: CancellationToken,
|
|
_: Uri | undefined,
|
|
multi: Uri[],
|
|
) => {
|
|
const maxQueryCount = MAX_QUERIES.getValue() as number;
|
|
const [files, dirFound] = await gatherQlFiles(
|
|
multi.map((uri) => uri.fsPath),
|
|
);
|
|
if (files.length > maxQueryCount) {
|
|
throw new Error(
|
|
`You tried to run ${files.length} queries, but the maximum is ${maxQueryCount}. Try selecting fewer queries or changing the 'codeQL.runningQueries.maxQueries' setting.`,
|
|
);
|
|
}
|
|
// warn user and display selected files when a directory is selected because some ql
|
|
// files may be hidden from the user.
|
|
if (dirFound) {
|
|
const fileString = files.map((file) => basename(file)).join(", ");
|
|
const res = await showBinaryChoiceDialog(
|
|
`You are about to run ${files.length} queries: ${fileString} Do you want to continue?`,
|
|
);
|
|
if (!res) {
|
|
return;
|
|
}
|
|
}
|
|
const queryUris = files.map((path) => Uri.parse(`file:${path}`, true));
|
|
|
|
// Use a wrapped progress so that messages appear with the queries remaining in it.
|
|
let queriesRemaining = queryUris.length;
|
|
function wrappedProgress(update: ProgressUpdate) {
|
|
const message =
|
|
queriesRemaining > 1
|
|
? `${queriesRemaining} remaining. ${update.message}`
|
|
: update.message;
|
|
progress({
|
|
...update,
|
|
message,
|
|
});
|
|
}
|
|
|
|
wrappedProgress({
|
|
maxStep: queryUris.length,
|
|
step: queryUris.length - queriesRemaining,
|
|
message: "",
|
|
});
|
|
|
|
await Promise.all(
|
|
queryUris.map(async (uri) =>
|
|
compileAndRunQuery(
|
|
false,
|
|
uri,
|
|
wrappedProgress,
|
|
token,
|
|
undefined,
|
|
).then(() => queriesRemaining--),
|
|
),
|
|
);
|
|
},
|
|
{
|
|
title: "Running queries",
|
|
cancellable: true,
|
|
},
|
|
|
|
// Open the query server logger on error since that's usually where the interesting errors appear.
|
|
queryServerLogger,
|
|
),
|
|
);
|
|
ctx.subscriptions.push(
|
|
commandRunnerWithProgress(
|
|
"codeQL.quickEval",
|
|
async (
|
|
progress: ProgressCallback,
|
|
token: CancellationToken,
|
|
uri: Uri | undefined,
|
|
) => await compileAndRunQuery(true, uri, progress, token, undefined),
|
|
{
|
|
title: "Running query",
|
|
cancellable: true,
|
|
},
|
|
// Open the query server logger on error since that's usually where the interesting errors appear.
|
|
queryServerLogger,
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunnerWithProgress(
|
|
"codeQL.codeLensQuickEval",
|
|
async (
|
|
progress: ProgressCallback,
|
|
token: CancellationToken,
|
|
uri: Uri,
|
|
range: Range,
|
|
) =>
|
|
await compileAndRunQuery(true, uri, progress, token, undefined, range),
|
|
{
|
|
title: "Running query",
|
|
cancellable: true,
|
|
},
|
|
|
|
// Open the query server logger on error since that's usually where the interesting errors appear.
|
|
queryServerLogger,
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunnerWithProgress(
|
|
"codeQL.quickQuery",
|
|
async (progress: ProgressCallback, token: CancellationToken) =>
|
|
displayQuickQuery(ctx, cliServer, databaseUI, progress, token),
|
|
{
|
|
title: "Run Quick Query",
|
|
},
|
|
|
|
// Open the query server logger on error since that's usually where the interesting errors appear.
|
|
queryServerLogger,
|
|
),
|
|
);
|
|
|
|
registerRemoteQueryTextProvider();
|
|
|
|
// The "runVariantAnalysis" command is internal-only.
|
|
ctx.subscriptions.push(
|
|
commandRunnerWithProgress(
|
|
"codeQL.runVariantAnalysis",
|
|
async (
|
|
progress: ProgressCallback,
|
|
token: CancellationToken,
|
|
uri: Uri | undefined,
|
|
) => {
|
|
if (isCanary()) {
|
|
progress({
|
|
maxStep: 5,
|
|
step: 0,
|
|
message: "Getting credentials",
|
|
});
|
|
|
|
await variantAnalysisManager.runVariantAnalysis(
|
|
uri || window.activeTextEditor?.document.uri,
|
|
progress,
|
|
token,
|
|
);
|
|
} else {
|
|
throw new Error(
|
|
"Variant analysis requires the CodeQL Canary version to run.",
|
|
);
|
|
}
|
|
},
|
|
{
|
|
title: "Run Variant Analysis",
|
|
cancellable: true,
|
|
},
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunner(
|
|
"codeQL.openVariantAnalysisLogs",
|
|
async (variantAnalysisId: number) => {
|
|
await variantAnalysisManager.openVariantAnalysisLogs(variantAnalysisId);
|
|
},
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunner(
|
|
"codeQL.copyVariantAnalysisRepoList",
|
|
async (
|
|
variantAnalysisId: number,
|
|
filterSort?: RepositoriesFilterSortStateWithIds,
|
|
) => {
|
|
await variantAnalysisManager.copyRepoListToClipboard(
|
|
variantAnalysisId,
|
|
filterSort,
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunner(
|
|
"codeQL.monitorVariantAnalysis",
|
|
async (variantAnalysis: VariantAnalysis, token: CancellationToken) => {
|
|
await variantAnalysisManager.monitorVariantAnalysis(
|
|
variantAnalysis,
|
|
token,
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunner(
|
|
"codeQL.autoDownloadVariantAnalysisResult",
|
|
async (
|
|
scannedRepo: VariantAnalysisScannedRepository,
|
|
variantAnalysisSummary: VariantAnalysis,
|
|
token: CancellationToken,
|
|
) => {
|
|
await variantAnalysisManager.enqueueDownload(
|
|
scannedRepo,
|
|
variantAnalysisSummary,
|
|
token,
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunner(
|
|
"codeQL.cancelVariantAnalysis",
|
|
async (variantAnalysisId: number) => {
|
|
await variantAnalysisManager.cancelVariantAnalysis(variantAnalysisId);
|
|
},
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunner("codeQL.exportSelectedVariantAnalysisResults", async () => {
|
|
await exportSelectedRemoteQueryResults(qhm);
|
|
}),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunner(
|
|
"codeQL.exportRemoteQueryResults",
|
|
async (queryId: string) => {
|
|
await exportRemoteQueryResults(qhm, rqm, queryId, app.credentials);
|
|
},
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunnerWithProgress(
|
|
"codeQL.exportVariantAnalysisResults",
|
|
async (
|
|
progress: ProgressCallback,
|
|
token: CancellationToken,
|
|
variantAnalysisId: number,
|
|
filterSort?: RepositoriesFilterSortStateWithIds,
|
|
) => {
|
|
await exportVariantAnalysisResults(
|
|
variantAnalysisManager,
|
|
variantAnalysisId,
|
|
filterSort,
|
|
app.credentials,
|
|
progress,
|
|
token,
|
|
);
|
|
},
|
|
{
|
|
title: "Exporting variant analysis results",
|
|
cancellable: true,
|
|
},
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunner(
|
|
"codeQL.loadVariantAnalysisRepoResults",
|
|
async (variantAnalysisId: number, repositoryFullName: string) => {
|
|
await variantAnalysisManager.loadResults(
|
|
variantAnalysisId,
|
|
repositoryFullName,
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
// The "openVariantAnalysisView" command is internal-only.
|
|
ctx.subscriptions.push(
|
|
commandRunner(
|
|
"codeQL.openVariantAnalysisView",
|
|
async (variantAnalysisId: number) => {
|
|
await variantAnalysisManager.showView(variantAnalysisId);
|
|
},
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunner(
|
|
"codeQL.openVariantAnalysisQueryText",
|
|
async (variantAnalysisId: number) => {
|
|
await variantAnalysisManager.openQueryText(variantAnalysisId);
|
|
},
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunner(
|
|
"codeQL.openVariantAnalysisQueryFile",
|
|
async (variantAnalysisId: number) => {
|
|
await variantAnalysisManager.openQueryFile(variantAnalysisId);
|
|
},
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunner("codeQL.openReferencedFile", openReferencedFile),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunner("codeQL.previewQueryHelp", previewQueryHelp),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunnerWithProgress(
|
|
"codeQL.restartQueryServer",
|
|
async (progress: ProgressCallback, token: CancellationToken) => {
|
|
// We restart the CLI server too, to ensure they are the same version
|
|
cliServer.restartCliServer();
|
|
await qs.restartQueryServer(progress, token);
|
|
void showAndLogInformationMessage("CodeQL Query Server restarted.", {
|
|
outputLogger: queryServerLogger,
|
|
});
|
|
},
|
|
{
|
|
title: "Restarting Query Server",
|
|
},
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunnerWithProgress(
|
|
"codeQL.chooseDatabaseFolder",
|
|
(progress: ProgressCallback, token: CancellationToken) =>
|
|
databaseUI.handleChooseDatabaseFolder(progress, token),
|
|
{
|
|
title: "Choose a Database from a Folder",
|
|
},
|
|
),
|
|
);
|
|
ctx.subscriptions.push(
|
|
commandRunnerWithProgress(
|
|
"codeQL.chooseDatabaseArchive",
|
|
(progress: ProgressCallback, token: CancellationToken) =>
|
|
databaseUI.handleChooseDatabaseArchive(progress, token),
|
|
{
|
|
title: "Choose a Database from an Archive",
|
|
},
|
|
),
|
|
);
|
|
ctx.subscriptions.push(
|
|
commandRunnerWithProgress(
|
|
"codeQL.chooseDatabaseGithub",
|
|
async (progress: ProgressCallback, token: CancellationToken) => {
|
|
const credentials = isCanary() ? app.credentials : undefined;
|
|
await databaseUI.handleChooseDatabaseGithub(
|
|
credentials,
|
|
progress,
|
|
token,
|
|
);
|
|
},
|
|
{
|
|
title: "Adding database from GitHub",
|
|
},
|
|
),
|
|
);
|
|
ctx.subscriptions.push(
|
|
commandRunnerWithProgress(
|
|
"codeQL.chooseDatabaseInternet",
|
|
(progress: ProgressCallback, token: CancellationToken) =>
|
|
databaseUI.handleChooseDatabaseInternet(progress, token),
|
|
|
|
{
|
|
title: "Adding database from URL",
|
|
},
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunner("codeQL.openDocumentation", async () =>
|
|
env.openExternal(Uri.parse("https://codeql.github.com/docs/")),
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunner("codeQL.copyVersion", async () => {
|
|
const text = `CodeQL extension version: ${
|
|
extension?.packageJSON.version
|
|
} \nCodeQL CLI version: ${await getCliVersion()} \nPlatform: ${platform()} ${arch()}`;
|
|
await env.clipboard.writeText(text);
|
|
void showAndLogInformationMessage(text);
|
|
}),
|
|
);
|
|
|
|
const getCliVersion = async () => {
|
|
try {
|
|
return await cliServer.getVersion();
|
|
} catch {
|
|
return "<missing>";
|
|
}
|
|
};
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunner("codeQL.authenticateToGitHub", async () => {
|
|
/**
|
|
* Credentials for authenticating to GitHub.
|
|
* These are used when making API calls.
|
|
*/
|
|
const octokit = await app.credentials.getOctokit();
|
|
const userInfo = await octokit.users.getAuthenticated();
|
|
void showAndLogInformationMessage(
|
|
`Authenticated to GitHub as user: ${userInfo.data.login}`,
|
|
);
|
|
}),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunnerWithProgress(
|
|
"codeQL.installPackDependencies",
|
|
async (progress: ProgressCallback) =>
|
|
await handleInstallPackDependencies(cliServer, progress),
|
|
{
|
|
title: "Installing pack dependencies",
|
|
},
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunnerWithProgress(
|
|
"codeQL.downloadPacks",
|
|
async (progress: ProgressCallback) =>
|
|
await handleDownloadPacks(cliServer, progress),
|
|
{
|
|
title: "Downloading packs",
|
|
},
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunner("codeQL.showLogs", async () => {
|
|
extLogger.show();
|
|
}),
|
|
);
|
|
|
|
ctx.subscriptions.push(new SummaryLanguageSupport());
|
|
|
|
void extLogger.log("Starting language server.");
|
|
await client.start();
|
|
ctx.subscriptions.push({
|
|
dispose: () => {
|
|
void client.stop();
|
|
},
|
|
});
|
|
// Jump-to-definition and find-references
|
|
void extLogger.log("Registering jump-to-definition handlers.");
|
|
|
|
// Store contextual queries in a temporary folder so that they are removed
|
|
// when the application closes. There is no need for the user to interact with them.
|
|
const contextualQueryStorageDir = join(
|
|
tmpDir.name,
|
|
"contextual-query-storage",
|
|
);
|
|
await ensureDir(contextualQueryStorageDir);
|
|
languages.registerDefinitionProvider(
|
|
{ scheme: zipArchiveScheme },
|
|
new TemplateQueryDefinitionProvider(
|
|
cliServer,
|
|
qs,
|
|
dbm,
|
|
contextualQueryStorageDir,
|
|
),
|
|
);
|
|
|
|
languages.registerReferenceProvider(
|
|
{ scheme: zipArchiveScheme },
|
|
new TemplateQueryReferenceProvider(
|
|
cliServer,
|
|
qs,
|
|
dbm,
|
|
contextualQueryStorageDir,
|
|
),
|
|
);
|
|
|
|
const astViewer = new AstViewer();
|
|
const printAstTemplateProvider = new TemplatePrintAstProvider(
|
|
cliServer,
|
|
qs,
|
|
dbm,
|
|
contextualQueryStorageDir,
|
|
);
|
|
const cfgTemplateProvider = new TemplatePrintCfgProvider(cliServer, dbm);
|
|
|
|
ctx.subscriptions.push(astViewer);
|
|
ctx.subscriptions.push(
|
|
commandRunnerWithProgress(
|
|
"codeQL.viewAst",
|
|
async (
|
|
progress: ProgressCallback,
|
|
token: CancellationToken,
|
|
selectedFile: Uri,
|
|
) => {
|
|
const ast = await printAstTemplateProvider.provideAst(
|
|
progress,
|
|
token,
|
|
selectedFile ?? window.activeTextEditor?.document.uri,
|
|
);
|
|
if (ast) {
|
|
astViewer.updateRoots(await ast.getRoots(), ast.db, ast.fileName);
|
|
}
|
|
},
|
|
{
|
|
cancellable: true,
|
|
title: "Calculate AST",
|
|
},
|
|
),
|
|
);
|
|
|
|
ctx.subscriptions.push(
|
|
commandRunnerWithProgress(
|
|
"codeQL.viewCfg",
|
|
async (progress: ProgressCallback, token: CancellationToken) => {
|
|
const res = await cfgTemplateProvider.provideCfgUri(
|
|
window.activeTextEditor?.document,
|
|
);
|
|
if (res) {
|
|
await compileAndRunQuery(false, res[0], progress, token, undefined);
|
|
}
|
|
},
|
|
{
|
|
title: "Calculating Control Flow Graph",
|
|
cancellable: true,
|
|
},
|
|
),
|
|
);
|
|
|
|
const mockServer = new VSCodeMockGitHubApiServer(ctx);
|
|
ctx.subscriptions.push(mockServer);
|
|
ctx.subscriptions.push(
|
|
commandRunner(
|
|
"codeQL.mockGitHubApiServer.startRecording",
|
|
async () => await mockServer.startRecording(),
|
|
),
|
|
);
|
|
ctx.subscriptions.push(
|
|
commandRunner(
|
|
"codeQL.mockGitHubApiServer.saveScenario",
|
|
async () => await mockServer.saveScenario(),
|
|
),
|
|
);
|
|
ctx.subscriptions.push(
|
|
commandRunner(
|
|
"codeQL.mockGitHubApiServer.cancelRecording",
|
|
async () => await mockServer.cancelRecording(),
|
|
),
|
|
);
|
|
ctx.subscriptions.push(
|
|
commandRunner(
|
|
"codeQL.mockGitHubApiServer.loadScenario",
|
|
async () => await mockServer.loadScenario(),
|
|
),
|
|
);
|
|
ctx.subscriptions.push(
|
|
commandRunner(
|
|
"codeQL.mockGitHubApiServer.unloadScenario",
|
|
async () => await mockServer.unloadScenario(),
|
|
),
|
|
);
|
|
|
|
await commands.executeCommand("codeQLDatabases.removeOrphanedDatabases");
|
|
|
|
void extLogger.log("Reading query history");
|
|
await qhm.readQueryHistory();
|
|
|
|
void extLogger.log("Successfully finished extension initialization.");
|
|
|
|
return {
|
|
ctx,
|
|
cliServer,
|
|
qs,
|
|
distributionManager,
|
|
databaseManager: dbm,
|
|
databaseUI,
|
|
variantAnalysisManager,
|
|
dispose: () => {
|
|
ctx.subscriptions.forEach((d) => d.dispose());
|
|
},
|
|
};
|
|
}
|
|
|
|
async function createQueryServer(
|
|
qlConfigurationListener: QueryServerConfigListener,
|
|
cliServer: CodeQLCliServer,
|
|
ctx: ExtensionContext,
|
|
): Promise<QueryRunner> {
|
|
const qsOpts = {
|
|
logger: queryServerLogger,
|
|
contextStoragePath: getContextStoragePath(ctx),
|
|
};
|
|
const progressCallback = (
|
|
task: (
|
|
progress: ProgressReporter,
|
|
token: CancellationToken,
|
|
) => Thenable<void>,
|
|
) =>
|
|
Window.withProgress(
|
|
{ title: "CodeQL query server", location: ProgressLocation.Window },
|
|
task,
|
|
);
|
|
if (await cliServer.cliConstraints.supportsNewQueryServer()) {
|
|
const qs = new QueryServerClient(
|
|
qlConfigurationListener,
|
|
cliServer,
|
|
qsOpts,
|
|
progressCallback,
|
|
);
|
|
ctx.subscriptions.push(qs);
|
|
await qs.startQueryServer();
|
|
return new NewQueryRunner(qs);
|
|
} else {
|
|
const qs = new LegacyQueryServerClient(
|
|
qlConfigurationListener,
|
|
cliServer,
|
|
qsOpts,
|
|
progressCallback,
|
|
);
|
|
ctx.subscriptions.push(qs);
|
|
await qs.startQueryServer();
|
|
return new LegacyQueryRunner(qs);
|
|
}
|
|
}
|
|
|
|
function getContextStoragePath(ctx: ExtensionContext) {
|
|
return ctx.storageUri?.fsPath || ctx.globalStorageUri.fsPath;
|
|
}
|
|
|
|
async function initializeLogging(ctx: ExtensionContext): Promise<void> {
|
|
ctx.subscriptions.push(extLogger);
|
|
ctx.subscriptions.push(queryServerLogger);
|
|
ctx.subscriptions.push(ideServerLogger);
|
|
}
|
|
|
|
const checkForUpdatesCommand = "codeQL.checkForUpdatesToCLI";
|
|
|
|
/**
|
|
* This text provider lets us open readonly files in the editor.
|
|
*
|
|
* TODO: Consolidate this with the 'codeql' text provider in query-history-manager.ts.
|
|
*/
|
|
function registerRemoteQueryTextProvider() {
|
|
workspace.registerTextDocumentContentProvider("remote-query", {
|
|
provideTextDocumentContent(uri: Uri): ProviderResult<string> {
|
|
const params = new URLSearchParams(uri.query);
|
|
|
|
return params.get("queryText");
|
|
},
|
|
});
|
|
}
|
|
|
|
const avoidVersionCheck = "avoid-version-check-at-startup";
|
|
const lastVersionChecked = "last-version-checked";
|
|
async function assertVSCodeVersionGreaterThan(
|
|
minVersion: string,
|
|
ctx: ExtensionContext,
|
|
) {
|
|
// Check if we should reset the version check.
|
|
const lastVersion = await ctx.globalState.get(lastVersionChecked);
|
|
await ctx.globalState.update(lastVersionChecked, vscodeVersion);
|
|
|
|
if (lastVersion !== minVersion) {
|
|
// In this case, the version has changed since the last time we checked.
|
|
// If the user has previously opted out of this check, then user has updated their
|
|
// vscode instance since then, so we should check again. Any future warning would
|
|
// be for a different version of vscode.
|
|
await ctx.globalState.update(avoidVersionCheck, false);
|
|
}
|
|
if (await ctx.globalState.get(avoidVersionCheck)) {
|
|
return;
|
|
}
|
|
try {
|
|
const parsedVersion = parse(vscodeVersion);
|
|
const parsedMinVersion = parse(minVersion);
|
|
if (!parsedVersion || !parsedMinVersion) {
|
|
void showAndLogWarningMessage(
|
|
`Could not do a version check of vscode because could not parse version number: actual vscode version ${vscodeVersion} or minimum supported vscode version ${minVersion}.`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (lt(parsedVersion, parsedMinVersion)) {
|
|
const message = `The CodeQL extension requires VS Code version ${minVersion} or later. Current version is ${vscodeVersion}. Please update VS Code to get the latest features of CodeQL.`;
|
|
const result = await showBinaryChoiceDialog(
|
|
message,
|
|
false,
|
|
"OK",
|
|
"Don't show again",
|
|
);
|
|
if (!result) {
|
|
await ctx.globalState.update(avoidVersionCheck, true);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
void showAndLogWarningMessage(
|
|
`Could not do a version check because of an error: ${getErrorMessage(e)}`,
|
|
);
|
|
}
|
|
}
|