Merge branch 'main' into robertbrignull/countSuccessfulRepos
This commit is contained in:
@@ -258,10 +258,10 @@ This requires running a MRVA query and seeing the results view.
|
||||
2. When the file does not exist
|
||||
7. Can open query text
|
||||
8. Can sort repos
|
||||
1. By name
|
||||
2. By results
|
||||
3. By stars
|
||||
4. By last updated
|
||||
1. Alphabetically
|
||||
2. By number of results
|
||||
3. By popularity
|
||||
4. By most recent commit
|
||||
9. Can filter repos
|
||||
10. Shows correct statistics
|
||||
1. Total number of results
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
## 1.8.3 - 26 April 2023
|
||||
|
||||
- Added ability to filter repositories for a variant analysis to only those that have results [#2343](https://github.com/github/vscode-codeql/pull/2343)
|
||||
- Add new configuration option to allow downloading databases from http, non-secure servers. [#2332](https://github.com/github/vscode-codeql/pull/2332)
|
||||
- Remove title actions from the query history panel that depended on history items being selected. [#2350](https://github.com/github/vscode-codeql/pull/2350)
|
||||
|
||||
## 1.8.2 - 12 April 2023
|
||||
|
||||
|
||||
4
extensions/ql-vscode/package-lock.json
generated
4
extensions/ql-vscode/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "vscode-codeql",
|
||||
"version": "1.8.3",
|
||||
"version": "1.8.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "vscode-codeql",
|
||||
"version": "1.8.3",
|
||||
"version": "1.8.4",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "CodeQL for Visual Studio Code",
|
||||
"author": "GitHub",
|
||||
"private": true,
|
||||
"version": "1.8.3",
|
||||
"version": "1.8.4",
|
||||
"publisher": "GitHub",
|
||||
"license": "MIT",
|
||||
"icon": "media/VS-marketplace-CodeQL-icon.png",
|
||||
@@ -340,6 +340,12 @@
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Allow database to be downloaded via HTTP. Warning: enabling this option will allow downloading from insecure servers."
|
||||
},
|
||||
"codeQL.createQuery.folder": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"patternErrorMessage": "Please enter a valid folder",
|
||||
"markdownDescription": "The name of the folder where we want to create queries and query packs via the \"CodeQL: Create Query\" command. The folder should exist."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -627,11 +633,6 @@
|
||||
"command": "codeQL.checkForUpdatesToCLI",
|
||||
"title": "CodeQL: Check for CLI Updates"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQueryTitleMenu",
|
||||
"title": "View Query",
|
||||
"icon": "$(edit)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQueryContextMenu",
|
||||
"title": "View Query",
|
||||
@@ -642,11 +643,6 @@
|
||||
"title": "Open Query Results",
|
||||
"icon": "$(preview)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItemTitleMenu",
|
||||
"title": "Delete",
|
||||
"icon": "$(trash)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItemContextMenu",
|
||||
"title": "Delete",
|
||||
@@ -847,21 +843,6 @@
|
||||
"when": "view == codeQLDatabases",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQueryTitleMenu",
|
||||
"when": "view == codeQLQueryHistory",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.itemClicked",
|
||||
"when": "view == codeQLQueryHistory",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItemTitleMenu",
|
||||
"when": "view == codeQLQueryHistory",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.sortByName",
|
||||
"when": "view == codeQLQueryHistory",
|
||||
@@ -1304,18 +1285,10 @@
|
||||
"command": "codeQLDatabases.upgradeDatabase",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQueryTitleMenu",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQueryContextMenu",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItemTitleMenu",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItemContextMenu",
|
||||
"when": "false"
|
||||
@@ -1446,7 +1419,7 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQL.createQuery",
|
||||
"when": "config.codeQL.canary"
|
||||
"when": "config.codeQL.codespacesTemplate"
|
||||
},
|
||||
{
|
||||
"command": "codeQLTests.acceptOutputContextTestItem",
|
||||
|
||||
@@ -171,6 +171,8 @@ export type OnLineCallback = (
|
||||
line: string,
|
||||
) => Promise<string | undefined> | string | undefined;
|
||||
|
||||
type VersionChangedListener = (newVersion: SemVer | undefined) => void;
|
||||
|
||||
/**
|
||||
* This class manages a cli server started by `codeql execute cli-server` to
|
||||
* run commands without the overhead of starting a new java
|
||||
@@ -188,7 +190,9 @@ export class CodeQLCliServer implements Disposable {
|
||||
nullBuffer: Buffer;
|
||||
|
||||
/** Version of current cli, lazily computed by the `getVersion()` method */
|
||||
private _version: Promise<SemVer> | undefined;
|
||||
private _version: SemVer | undefined;
|
||||
|
||||
private _versionChangedListeners: VersionChangedListener[] = [];
|
||||
|
||||
/**
|
||||
* The languages supported by the current version of the CLI, computed by `getSupportedLanguages()`.
|
||||
@@ -1417,15 +1421,36 @@ export class CodeQLCliServer implements Disposable {
|
||||
|
||||
public async getVersion() {
|
||||
if (!this._version) {
|
||||
this._version = this.refreshVersion();
|
||||
// this._version is only undefined upon config change, so we reset CLI-based context key only when necessary.
|
||||
await this.app.commands.execute(
|
||||
"setContext",
|
||||
"codeql.supportsEvalLog",
|
||||
await this.cliConstraints.supportsPerQueryEvalLog(),
|
||||
);
|
||||
try {
|
||||
const newVersion = await this.refreshVersion();
|
||||
this._version = newVersion;
|
||||
this._versionChangedListeners.forEach((listener) =>
|
||||
listener(newVersion),
|
||||
);
|
||||
|
||||
// this._version is only undefined upon config change, so we reset CLI-based context key only when necessary.
|
||||
await this.app.commands.execute(
|
||||
"setContext",
|
||||
"codeql.supportsEvalLog",
|
||||
newVersion.compare(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_PER_QUERY_EVAL_LOG,
|
||||
) >= 0,
|
||||
);
|
||||
} catch (e) {
|
||||
this._versionChangedListeners.forEach((listener) =>
|
||||
listener(undefined),
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return await this._version;
|
||||
return this._version;
|
||||
}
|
||||
|
||||
public addVersionChangedListener(listener: VersionChangedListener) {
|
||||
if (this._version) {
|
||||
listener(this._version);
|
||||
}
|
||||
this._versionChangedListeners.push(listener);
|
||||
}
|
||||
|
||||
private async refreshVersion() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { CommandManager } from "../packages/commands";
|
||||
import type { Uri, Range, TextDocumentShowOptions } from "vscode";
|
||||
import type { AstItem } from "../astViewer";
|
||||
import type { AstItem } from "../language-support";
|
||||
import type { DbTreeViewItem } from "../databases/ui/db-tree-view-item";
|
||||
import type { DatabaseItem } from "../local-databases";
|
||||
import type { DatabaseItem } from "../databases/local-databases";
|
||||
import type { QueryHistoryInfo } from "../query-history/query-history-info";
|
||||
import type { RepositoriesFilterSortStateWithIds } from "../pure/variant-analysis-filter-sort";
|
||||
import type { TestTreeNode } from "../test-tree-node";
|
||||
@@ -13,21 +13,6 @@ import type {
|
||||
} from "../variant-analysis/shared/variant-analysis";
|
||||
import type { QLDebugConfiguration } from "../debugger/debug-configuration";
|
||||
|
||||
// A command function matching the signature that VS Code calls when
|
||||
// a command is invoked from the title bar of a TreeView with
|
||||
// canSelectMany set to true.
|
||||
//
|
||||
// It is possible to get any combination of singleItem and multiSelect
|
||||
// to be undefined. This is because it is possible to click a title bar
|
||||
// option without interacting with any individual items first, or even
|
||||
// when there are no items present at all.
|
||||
// If both singleItem and multiSelect are defined, then singleItem will
|
||||
// be contained within multiSelect.
|
||||
export type TreeViewTitleMultiSelectionCommandFunction<Item> = (
|
||||
singleItem: Item | undefined,
|
||||
multiSelect: Item[] | undefined,
|
||||
) => Promise<void>;
|
||||
|
||||
// A command function matching the signature that VS Code calls when
|
||||
// a command is invoked from a context menu on a TreeView with
|
||||
// canSelectMany set to true.
|
||||
@@ -176,9 +161,7 @@ export type QueryHistoryCommands = {
|
||||
"codeQLQueryHistory.sortByCount": () => Promise<void>;
|
||||
|
||||
// Commands in the context menu or in the hover menu
|
||||
"codeQLQueryHistory.openQueryTitleMenu": TreeViewTitleMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.openQueryContextMenu": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.removeHistoryItemTitleMenu": TreeViewTitleMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.removeHistoryItemContextMenu": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.removeHistoryItemContextInline": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.renameItem": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
@@ -195,7 +178,7 @@ export type QueryHistoryCommands = {
|
||||
"codeQLQueryHistory.viewCsvAlerts": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.viewSarifAlerts": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.viewDil": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.itemClicked": TreeViewTitleMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.itemClicked": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.openOnGithub": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.copyRepoList": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
|
||||
|
||||
51
extensions/ql-vscode/src/common/selection-commands.ts
Normal file
51
extensions/ql-vscode/src/common/selection-commands.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { showAndLogErrorMessage } from "../helpers";
|
||||
import {
|
||||
ExplorerSelectionCommandFunction,
|
||||
TreeViewContextMultiSelectionCommandFunction,
|
||||
TreeViewContextSingleSelectionCommandFunction,
|
||||
} from "./commands";
|
||||
|
||||
// A hack to match types that are not an array, which is useful to help avoid
|
||||
// misusing createSingleSelectionCommand, e.g. where T accidentally gets instantiated
|
||||
// as DatabaseItem[] instead of DatabaseItem.
|
||||
type NotArray = object & { length?: never };
|
||||
|
||||
// A way to get the type system to help assert that one type is a supertype of another.
|
||||
type CreateSupertypeOf<Super, Sub extends Super> = Sub;
|
||||
|
||||
// This asserts that SelectionCommand is assignable to all of the different types of
|
||||
// SelectionCommand defined in commands.ts. The intention is the output from the helpers
|
||||
// in this file can be used with any of the select command types and can handle any of
|
||||
// the inputs.
|
||||
type SelectionCommand<T extends NotArray> = CreateSupertypeOf<
|
||||
TreeViewContextMultiSelectionCommandFunction<T> &
|
||||
TreeViewContextSingleSelectionCommandFunction<T> &
|
||||
ExplorerSelectionCommandFunction<T>,
|
||||
(singleItem: T, multiSelect?: T[] | undefined) => Promise<void>
|
||||
>;
|
||||
|
||||
export function createSingleSelectionCommand<T extends NotArray>(
|
||||
f: (argument: T) => Promise<void>,
|
||||
itemName: string,
|
||||
): SelectionCommand<T> {
|
||||
return async (singleItem, multiSelect) => {
|
||||
if (multiSelect === undefined || multiSelect.length === 1) {
|
||||
return f(singleItem);
|
||||
} else {
|
||||
void showAndLogErrorMessage(`Please select a single ${itemName}.`);
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function createMultiSelectionCommand<T extends NotArray>(
|
||||
f: (argument: T[]) => Promise<void>,
|
||||
): SelectionCommand<T> {
|
||||
return async (singleItem, multiSelect) => {
|
||||
if (multiSelect !== undefined && multiSelect.length > 0) {
|
||||
return f(multiSelect);
|
||||
} else {
|
||||
return f([singleItem]);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "../pure/interface-types";
|
||||
import { Logger } from "../common";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { DatabaseManager } from "../local-databases";
|
||||
import { DatabaseManager } from "../databases/local-databases";
|
||||
import { jumpToLocation } from "../interface-utils";
|
||||
import {
|
||||
transformBqrsResultSet,
|
||||
|
||||
@@ -619,3 +619,19 @@ export const ALLOW_HTTP_SETTING = new Setting(
|
||||
export function allowHttp(): boolean {
|
||||
return ALLOW_HTTP_SETTING.getValue<boolean>() || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the folder where we want to create skeleton wizard QL packs.
|
||||
**/
|
||||
const SKELETON_WIZARD_FOLDER = new Setting(
|
||||
"folder",
|
||||
new Setting("createQuery", ROOT_SETTING),
|
||||
);
|
||||
|
||||
export function getSkeletonWizardFolder(): string | undefined {
|
||||
return SKELETON_WIZARD_FOLDER.getValue<string>() || undefined;
|
||||
}
|
||||
|
||||
export async function setSkeletonWizardFolder(folder: string | undefined) {
|
||||
await SKELETON_WIZARD_FOLDER.updateValue(folder, ConfigurationTarget.Global);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ExtensionContext } from "vscode";
|
||||
import { DataExtensionsEditorView } from "./data-extensions-editor-view";
|
||||
import { DataExtensionsEditorCommands } from "../common/commands";
|
||||
import { CliVersionConstraint, CodeQLCliServer } from "../cli";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { DatabaseManager } from "../local-databases";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { DatabaseManager } from "../databases/local-databases";
|
||||
import { ensureDir } from "fs-extra";
|
||||
import { join } from "path";
|
||||
import { App } from "../common/app";
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
ToDataExtensionsEditorMessage,
|
||||
} from "../pure/interface-types";
|
||||
import { ProgressUpdate } from "../progress";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import {
|
||||
showAndLogErrorMessage,
|
||||
showAndLogExceptionWithTelemetry,
|
||||
@@ -21,11 +21,11 @@ import {
|
||||
import { extLogger } from "../common";
|
||||
import { outputFile, pathExists, readFile } from "fs-extra";
|
||||
import { load as loadYaml } from "js-yaml";
|
||||
import { DatabaseItem, DatabaseManager } from "../local-databases";
|
||||
import { DatabaseItem, DatabaseManager } from "../databases/local-databases";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { asError, assertNever, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { generateFlowModel } from "./generate-flow-model";
|
||||
import { promptImportGithubDatabase } from "../databaseFetcher";
|
||||
import { promptImportGithubDatabase } from "../databases/database-fetcher";
|
||||
import { App } from "../common/app";
|
||||
import { ResolvableLocationValue } from "../pure/bqrs-cli-types";
|
||||
import { showResolvableLocation } from "../interface-utils";
|
||||
@@ -35,7 +35,7 @@ import { readQueryResults, runQuery } from "./external-api-usage-query";
|
||||
import { createDataExtensionYaml, loadDataExtensionYaml } from "./yaml";
|
||||
import { ExternalApiUsage } from "./external-api-usage";
|
||||
import { ModeledMethod } from "./modeled-method";
|
||||
import { ExtensionPackModelFile } from "./extension-pack-picker";
|
||||
import { ExtensionPackModelFile } from "./shared/extension-pack";
|
||||
|
||||
function getQlSubmoduleFolder(): WorkspaceFolder | undefined {
|
||||
const workspaceFolder = workspace.workspaceFolders?.find(
|
||||
@@ -118,7 +118,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
msg.externalApiUsages,
|
||||
msg.modeledMethods,
|
||||
);
|
||||
await this.loadExternalApiUsages();
|
||||
await Promise.all([this.setViewState(), this.loadExternalApiUsages()]);
|
||||
|
||||
break;
|
||||
case "generateExternalApi":
|
||||
@@ -134,16 +134,22 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
super.onWebViewLoaded();
|
||||
|
||||
await Promise.all([
|
||||
this.postMessage({
|
||||
t: "setDataExtensionEditorInitialData",
|
||||
extensionPackName: this.modelFile.extensionPack.name,
|
||||
modelFilename: this.modelFile.filename,
|
||||
}),
|
||||
this.setViewState(),
|
||||
this.loadExternalApiUsages(),
|
||||
this.loadExistingModeledMethods(),
|
||||
]);
|
||||
}
|
||||
|
||||
private async setViewState(): Promise<void> {
|
||||
await this.postMessage({
|
||||
t: "setDataExtensionEditorViewState",
|
||||
viewState: {
|
||||
extensionPackModelFile: this.modelFile,
|
||||
modelFileExists: await pathExists(this.modelFile.filename),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected async jumpToUsage(
|
||||
location: ResolvableLocationValue,
|
||||
): Promise<void> {
|
||||
|
||||
@@ -10,8 +10,10 @@ import {
|
||||
showAndLogErrorMessage,
|
||||
} from "../helpers";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { DatabaseItem } from "../databases/local-databases";
|
||||
import { getQlPackPath, QLPACK_FILENAMES } from "../pure/ql";
|
||||
import { getErrorMessage } from "../pure/helpers-pure";
|
||||
import { ExtensionPack, ExtensionPackModelFile } from "./shared/extension-pack";
|
||||
|
||||
const maxStep = 3;
|
||||
|
||||
@@ -21,16 +23,6 @@ const packNameRegex = new RegExp(
|
||||
);
|
||||
const packNameLength = 128;
|
||||
|
||||
export interface ExtensionPack {
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface ExtensionPackModelFile {
|
||||
filename: string;
|
||||
extensionPack: ExtensionPack;
|
||||
}
|
||||
|
||||
export async function pickExtensionPackModelFile(
|
||||
cliServer: Pick<CodeQLCliServer, "resolveQlpacks" | "resolveExtensions">,
|
||||
databaseItem: Pick<DatabaseItem, "name" | "language">,
|
||||
@@ -50,7 +42,7 @@ export async function pickExtensionPackModelFile(
|
||||
const modelFile = await pickModelFile(
|
||||
cliServer,
|
||||
databaseItem,
|
||||
extensionPack.path,
|
||||
extensionPack,
|
||||
progress,
|
||||
token,
|
||||
);
|
||||
@@ -78,19 +70,72 @@ async function pickExtensionPack(
|
||||
|
||||
// Get all existing extension packs in the workspace
|
||||
const additionalPacks = getOnDiskWorkspaceFolders();
|
||||
const extensionPacks = await cliServer.resolveQlpacks(additionalPacks, true);
|
||||
const extensionPacksInfo = await cliServer.resolveQlpacks(
|
||||
additionalPacks,
|
||||
true,
|
||||
);
|
||||
|
||||
if (Object.keys(extensionPacks).length === 0) {
|
||||
if (Object.keys(extensionPacksInfo).length === 0) {
|
||||
return pickNewExtensionPack(databaseItem, token);
|
||||
}
|
||||
|
||||
const options: Array<{ label: string; extensionPack: string | null }> =
|
||||
Object.keys(extensionPacks).map((pack) => ({
|
||||
label: pack,
|
||||
extensionPack: pack,
|
||||
}));
|
||||
const extensionPacks = (
|
||||
await Promise.all(
|
||||
Object.entries(extensionPacksInfo).map(async ([name, paths]) => {
|
||||
if (paths.length !== 1) {
|
||||
void showAndLogErrorMessage(
|
||||
`Extension pack ${name} resolves to multiple paths`,
|
||||
{
|
||||
fullMessage: `Extension pack ${name} resolves to multiple paths: ${paths.join(
|
||||
", ",
|
||||
)}`,
|
||||
},
|
||||
);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const path = paths[0];
|
||||
|
||||
let extensionPack: ExtensionPack;
|
||||
try {
|
||||
extensionPack = await readExtensionPack(path);
|
||||
} catch (e: unknown) {
|
||||
void showAndLogErrorMessage(`Could not read extension pack ${name}`, {
|
||||
fullMessage: `Could not read extension pack ${name} at ${path}: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
});
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return extensionPack;
|
||||
}),
|
||||
)
|
||||
).filter((info): info is ExtensionPack => info !== undefined);
|
||||
|
||||
const extensionPacksForLanguage = extensionPacks.filter(
|
||||
(pack) =>
|
||||
pack.extensionTargets[`codeql/${databaseItem.language}-all`] !==
|
||||
undefined,
|
||||
);
|
||||
|
||||
const options: Array<{
|
||||
label: string;
|
||||
description: string | undefined;
|
||||
detail: string | undefined;
|
||||
extensionPack: ExtensionPack | null;
|
||||
}> = extensionPacksForLanguage.map((pack) => ({
|
||||
label: pack.name,
|
||||
description: pack.version,
|
||||
detail: pack.path,
|
||||
extensionPack: pack,
|
||||
}));
|
||||
options.push({
|
||||
label: "Create new extension pack",
|
||||
description: undefined,
|
||||
detail: undefined,
|
||||
extensionPack: null,
|
||||
});
|
||||
|
||||
@@ -115,57 +160,39 @@ async function pickExtensionPack(
|
||||
return pickNewExtensionPack(databaseItem, token);
|
||||
}
|
||||
|
||||
const extensionPackPaths = extensionPacks[extensionPackOption.extensionPack];
|
||||
if (extensionPackPaths.length !== 1) {
|
||||
void showAndLogErrorMessage(
|
||||
`Extension pack ${extensionPackOption.extensionPack} could not be resolved to a single location`,
|
||||
{
|
||||
fullMessage: `Extension pack ${
|
||||
extensionPackOption.extensionPack
|
||||
} could not be resolved to a single location. Found ${
|
||||
extensionPackPaths.length
|
||||
} locations: ${extensionPackPaths.join(", ")}.`,
|
||||
},
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
name: extensionPackOption.extensionPack,
|
||||
path: extensionPackPaths[0],
|
||||
};
|
||||
return extensionPackOption.extensionPack;
|
||||
}
|
||||
|
||||
async function pickModelFile(
|
||||
cliServer: Pick<CodeQLCliServer, "resolveExtensions">,
|
||||
databaseItem: Pick<DatabaseItem, "name">,
|
||||
extensionPackPath: string,
|
||||
extensionPack: ExtensionPack,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<string | undefined> {
|
||||
// Find the existing model files in the extension pack
|
||||
const additionalPacks = getOnDiskWorkspaceFolders();
|
||||
const extensions = await cliServer.resolveExtensions(
|
||||
extensionPackPath,
|
||||
extensionPack.path,
|
||||
additionalPacks,
|
||||
);
|
||||
|
||||
const modelFiles = new Set<string>();
|
||||
|
||||
if (extensionPackPath in extensions.data) {
|
||||
for (const extension of extensions.data[extensionPackPath]) {
|
||||
if (extensionPack.path in extensions.data) {
|
||||
for (const extension of extensions.data[extensionPack.path]) {
|
||||
modelFiles.add(extension.file);
|
||||
}
|
||||
}
|
||||
|
||||
if (modelFiles.size === 0) {
|
||||
return pickNewModelFile(databaseItem, extensionPackPath, token);
|
||||
return pickNewModelFile(databaseItem, extensionPack, token);
|
||||
}
|
||||
|
||||
const fileOptions: Array<{ label: string; file: string | null }> = [];
|
||||
for (const file of modelFiles) {
|
||||
fileOptions.push({
|
||||
label: relative(extensionPackPath, file).replaceAll(sep, "/"),
|
||||
label: relative(extensionPack.path, file).replaceAll(sep, "/"),
|
||||
file,
|
||||
});
|
||||
}
|
||||
@@ -196,7 +223,7 @@ async function pickModelFile(
|
||||
return fileOption.file;
|
||||
}
|
||||
|
||||
return pickNewModelFile(databaseItem, extensionPackPath, token);
|
||||
return pickNewModelFile(databaseItem, extensionPack, token);
|
||||
}
|
||||
|
||||
async function pickNewExtensionPack(
|
||||
@@ -266,66 +293,36 @@ async function pickNewExtensionPack(
|
||||
|
||||
const packYamlPath = join(packPath, "codeql-pack.yml");
|
||||
|
||||
const extensionPack: ExtensionPack = {
|
||||
path: packPath,
|
||||
yamlPath: packYamlPath,
|
||||
name,
|
||||
version: "0.0.0",
|
||||
extensionTargets: {
|
||||
[`codeql/${databaseItem.language}-all`]: "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml"],
|
||||
};
|
||||
|
||||
await outputFile(
|
||||
packYamlPath,
|
||||
dumpYaml({
|
||||
name,
|
||||
version: "0.0.0",
|
||||
name: extensionPack.name,
|
||||
version: extensionPack.version,
|
||||
library: true,
|
||||
extensionTargets: {
|
||||
[`codeql/${databaseItem.language}-all`]: "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml"],
|
||||
extensionTargets: extensionPack.extensionTargets,
|
||||
dataExtensions: extensionPack.dataExtensions,
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
name: packName,
|
||||
path: packPath,
|
||||
};
|
||||
return extensionPack;
|
||||
}
|
||||
|
||||
async function pickNewModelFile(
|
||||
databaseItem: Pick<DatabaseItem, "name">,
|
||||
extensionPackPath: string,
|
||||
extensionPack: ExtensionPack,
|
||||
token: CancellationToken,
|
||||
) {
|
||||
const qlpackPath = await getQlPackPath(extensionPackPath);
|
||||
if (!qlpackPath) {
|
||||
void showAndLogErrorMessage(
|
||||
`Could not find any of ${QLPACK_FILENAMES.join(
|
||||
", ",
|
||||
)} in ${extensionPackPath}`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const qlpack = await loadYaml(await readFile(qlpackPath, "utf8"), {
|
||||
filename: qlpackPath,
|
||||
});
|
||||
if (typeof qlpack !== "object" || qlpack === null) {
|
||||
void showAndLogErrorMessage(`Could not parse ${qlpackPath}`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const dataExtensionPatternsValue = qlpack.dataExtensions;
|
||||
if (
|
||||
!(
|
||||
Array.isArray(dataExtensionPatternsValue) ||
|
||||
typeof dataExtensionPatternsValue === "string"
|
||||
)
|
||||
) {
|
||||
void showAndLogErrorMessage(
|
||||
`Expected 'dataExtensions' to be a string or an array in ${qlpackPath}`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// The YAML allows either a string or an array of strings
|
||||
const dataExtensionPatterns = Array.isArray(dataExtensionPatternsValue)
|
||||
? dataExtensionPatternsValue
|
||||
: [dataExtensionPatternsValue];
|
||||
|
||||
const filename = await window.showInputBox(
|
||||
{
|
||||
title: "Enter the name of the new model file",
|
||||
@@ -335,24 +332,25 @@ async function pickNewModelFile(
|
||||
return "File name must not be empty";
|
||||
}
|
||||
|
||||
const path = resolve(extensionPackPath, value);
|
||||
const path = resolve(extensionPack.path, value);
|
||||
|
||||
if (await pathExists(path)) {
|
||||
return "File already exists";
|
||||
}
|
||||
|
||||
const notInExtensionPack = relative(extensionPackPath, path).startsWith(
|
||||
"..",
|
||||
);
|
||||
const notInExtensionPack = relative(
|
||||
extensionPack.path,
|
||||
path,
|
||||
).startsWith("..");
|
||||
if (notInExtensionPack) {
|
||||
return "File must be in the extension pack";
|
||||
}
|
||||
|
||||
const matchesPattern = dataExtensionPatterns.some((pattern) =>
|
||||
const matchesPattern = extensionPack.dataExtensions.some((pattern) =>
|
||||
minimatch(value, pattern, { matchBase: true }),
|
||||
);
|
||||
if (!matchesPattern) {
|
||||
return `File must match one of the patterns in 'dataExtensions' in ${qlpackPath}`;
|
||||
return `File must match one of the patterns in 'dataExtensions' in ${extensionPack.yamlPath}`;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -364,5 +362,47 @@ async function pickNewModelFile(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return resolve(extensionPackPath, filename);
|
||||
return resolve(extensionPack.path, filename);
|
||||
}
|
||||
|
||||
async function readExtensionPack(path: string): Promise<ExtensionPack> {
|
||||
const qlpackPath = await getQlPackPath(path);
|
||||
if (!qlpackPath) {
|
||||
throw new Error(
|
||||
`Could not find any of ${QLPACK_FILENAMES.join(", ")} in ${path}`,
|
||||
);
|
||||
}
|
||||
|
||||
const qlpack = await loadYaml(await readFile(qlpackPath, "utf8"), {
|
||||
filename: qlpackPath,
|
||||
});
|
||||
if (typeof qlpack !== "object" || qlpack === null) {
|
||||
throw new Error(`Could not parse ${qlpackPath}`);
|
||||
}
|
||||
|
||||
const dataExtensionValue = qlpack.dataExtensions;
|
||||
if (
|
||||
!(
|
||||
Array.isArray(dataExtensionValue) ||
|
||||
typeof dataExtensionValue === "string"
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
`Expected 'dataExtensions' to be a string or an array in ${qlpackPath}`,
|
||||
);
|
||||
}
|
||||
|
||||
// The YAML allows either a string or an array of strings
|
||||
const dataExtensions = Array.isArray(dataExtensionValue)
|
||||
? dataExtensionValue
|
||||
: [dataExtensionValue];
|
||||
|
||||
return {
|
||||
path,
|
||||
yamlPath: qlpackPath,
|
||||
name: qlpack.name,
|
||||
version: qlpack.version,
|
||||
extensionTargets: qlpack.extensionTargets,
|
||||
dataExtensions,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CoreCompletedQuery, QueryRunner } from "../queryRunner";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../query-server";
|
||||
import { dir } from "tmp-promise";
|
||||
import { writeFile } from "fs-extra";
|
||||
import { dump as dumpYaml } from "js-yaml";
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { TeeLogger } from "../common";
|
||||
import { CancellationToken } from "vscode";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { DatabaseItem } from "../databases/local-databases";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import { fetchExternalApiQueries } from "./queries";
|
||||
import { QueryResultType } from "../pure/new-messages";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CancellationToken } from "vscode";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { DatabaseItem } from "../databases/local-databases";
|
||||
import { join } from "path";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { TeeLogger } from "../common";
|
||||
import { extensiblePredicateDefinitions } from "./predicates";
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export interface ExtensionPack {
|
||||
path: string;
|
||||
yamlPath: string;
|
||||
|
||||
name: string;
|
||||
version: string;
|
||||
|
||||
extensionTargets: Record<string, string>;
|
||||
dataExtensions: string[];
|
||||
}
|
||||
|
||||
export interface ExtensionPackModelFile {
|
||||
filename: string;
|
||||
extensionPack: ExtensionPack;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ExtensionPackModelFile } from "./extension-pack";
|
||||
|
||||
export interface DataExtensionEditorViewState {
|
||||
extensionPackModelFile: ExtensionPackModelFile;
|
||||
modelFileExists: boolean;
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import fetch, { Response } from "node-fetch";
|
||||
import { zip } from "zip-a-folder";
|
||||
import { Open } from "unzipper";
|
||||
import { Uri, CancellationToken, window, InputBoxOptions } from "vscode";
|
||||
import { CodeQLCliServer } from "./cli";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import {
|
||||
ensureDir,
|
||||
realpath as fs_realpath,
|
||||
@@ -17,17 +17,17 @@ import * as Octokit from "@octokit/rest";
|
||||
import { retry } from "@octokit/plugin-retry";
|
||||
|
||||
import { DatabaseManager, DatabaseItem } from "./local-databases";
|
||||
import { showAndLogInformationMessage, tmpDir } from "./helpers";
|
||||
import { reportStreamProgress, ProgressCallback } from "./progress";
|
||||
import { extLogger } from "./common";
|
||||
import { getErrorMessage } from "./pure/helpers-pure";
|
||||
import { showAndLogInformationMessage, tmpDir } from "../helpers";
|
||||
import { reportStreamProgress, ProgressCallback } from "../progress";
|
||||
import { extLogger } from "../common";
|
||||
import { getErrorMessage } from "../pure/helpers-pure";
|
||||
import {
|
||||
getNwoFromGitHubUrl,
|
||||
isValidGitHubNwo,
|
||||
} from "./common/github-url-identifier-helper";
|
||||
import { Credentials } from "./common/authentication";
|
||||
import { AppCommandManager } from "./common/commands";
|
||||
import { ALLOW_HTTP_SETTING } from "./config";
|
||||
} from "../common/github-url-identifier-helper";
|
||||
import { Credentials } from "../common/authentication";
|
||||
import { AppCommandManager } from "../common/commands";
|
||||
import { ALLOW_HTTP_SETTING } from "../config";
|
||||
|
||||
/**
|
||||
* Prompts a user to fetch a database from a remote location. Database is assumed to be an archive file.
|
||||
@@ -317,13 +317,15 @@ async function databaseArchiveFetcher(
|
||||
});
|
||||
await ensureZippedSourceLocation(dbPath);
|
||||
|
||||
const makeSelected = true;
|
||||
|
||||
const item = await databaseManager.openDatabase(
|
||||
progress,
|
||||
token,
|
||||
Uri.file(dbPath),
|
||||
makeSelected,
|
||||
nameOverride,
|
||||
);
|
||||
await databaseManager.setCurrentDatabaseItem(item);
|
||||
return item;
|
||||
} else {
|
||||
throw new Error("Database not found in archive.");
|
||||
@@ -1,5 +1,5 @@
|
||||
import { join, basename, dirname as path_dirname } from "path";
|
||||
import { DisposableObject } from "./pure/disposable-object";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import {
|
||||
Event,
|
||||
EventEmitter,
|
||||
@@ -27,25 +27,29 @@ import {
|
||||
ProgressContext,
|
||||
withInheritedProgress,
|
||||
withProgress,
|
||||
} from "./progress";
|
||||
} from "../progress";
|
||||
import {
|
||||
isLikelyDatabaseRoot,
|
||||
isLikelyDbLanguageFolder,
|
||||
showAndLogErrorMessage,
|
||||
showAndLogExceptionWithTelemetry,
|
||||
} from "./helpers";
|
||||
import { extLogger } from "./common";
|
||||
} from "../helpers";
|
||||
import { extLogger } from "../common";
|
||||
import {
|
||||
importArchiveDatabase,
|
||||
promptImportGithubDatabase,
|
||||
promptImportInternetDatabase,
|
||||
} from "./databaseFetcher";
|
||||
import { asError, asyncFilter, getErrorMessage } from "./pure/helpers-pure";
|
||||
import { QueryRunner } from "./queryRunner";
|
||||
import { isCanary } from "./config";
|
||||
import { App } from "./common/app";
|
||||
import { redactableError } from "./pure/errors";
|
||||
import { LocalDatabasesCommands } from "./common/commands";
|
||||
} from "./database-fetcher";
|
||||
import { asError, asyncFilter, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { isCanary } from "../config";
|
||||
import { App } from "../common/app";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { LocalDatabasesCommands } from "../common/commands";
|
||||
import {
|
||||
createMultiSelectionCommand,
|
||||
createSingleSelectionCommand,
|
||||
} from "../common/selection-commands";
|
||||
|
||||
enum SortOrder {
|
||||
NameAsc = "NameAsc",
|
||||
@@ -240,11 +244,22 @@ export class DatabaseUI extends DisposableObject {
|
||||
this.handleMakeCurrentDatabase.bind(this),
|
||||
"codeQLDatabases.sortByName": this.handleSortByName.bind(this),
|
||||
"codeQLDatabases.sortByDateAdded": this.handleSortByDateAdded.bind(this),
|
||||
"codeQLDatabases.removeDatabase": this.handleRemoveDatabase.bind(this),
|
||||
"codeQLDatabases.upgradeDatabase": this.handleUpgradeDatabase.bind(this),
|
||||
"codeQLDatabases.renameDatabase": this.handleRenameDatabase.bind(this),
|
||||
"codeQLDatabases.openDatabaseFolder": this.handleOpenFolder.bind(this),
|
||||
"codeQLDatabases.addDatabaseSource": this.handleAddSource.bind(this),
|
||||
"codeQLDatabases.removeDatabase": createMultiSelectionCommand(
|
||||
this.handleRemoveDatabase.bind(this),
|
||||
),
|
||||
"codeQLDatabases.upgradeDatabase": createMultiSelectionCommand(
|
||||
this.handleUpgradeDatabase.bind(this),
|
||||
),
|
||||
"codeQLDatabases.renameDatabase": createSingleSelectionCommand(
|
||||
this.handleRenameDatabase.bind(this),
|
||||
"database",
|
||||
),
|
||||
"codeQLDatabases.openDatabaseFolder": createMultiSelectionCommand(
|
||||
this.handleOpenFolder.bind(this),
|
||||
),
|
||||
"codeQLDatabases.addDatabaseSource": createMultiSelectionCommand(
|
||||
this.handleAddSource.bind(this),
|
||||
),
|
||||
"codeQLDatabases.removeOrphanedDatabases":
|
||||
this.handleRemoveOrphanedDatabases.bind(this),
|
||||
};
|
||||
@@ -306,18 +321,21 @@ export class DatabaseUI extends DisposableObject {
|
||||
`${workspace.workspaceFolders[0].uri}/.tours/codeql-tutorial-database`,
|
||||
);
|
||||
|
||||
let databaseItem = this.databaseManager.findDatabaseItem(uri);
|
||||
const isTutorialDatabase = true;
|
||||
const databaseItem = this.databaseManager.findDatabaseItem(uri);
|
||||
if (databaseItem === undefined) {
|
||||
databaseItem = await this.databaseManager.openDatabase(
|
||||
const makeSelected = true;
|
||||
const nameOverride = "CodeQL Tutorial Database";
|
||||
const isTutorialDatabase = true;
|
||||
|
||||
await this.databaseManager.openDatabase(
|
||||
progress,
|
||||
token,
|
||||
uri,
|
||||
"CodeQL Tutorial Database",
|
||||
makeSelected,
|
||||
nameOverride,
|
||||
isTutorialDatabase,
|
||||
);
|
||||
}
|
||||
await this.databaseManager.setCurrentDatabaseItem(databaseItem);
|
||||
await this.handleTourDependencies();
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -512,12 +530,11 @@ export class DatabaseUI extends DisposableObject {
|
||||
private async handleUpgradeCurrentDatabase(): Promise<void> {
|
||||
return withProgress(
|
||||
async (progress, token) => {
|
||||
await this.handleUpgradeDatabaseInternal(
|
||||
progress,
|
||||
token,
|
||||
this.databaseManager.currentDatabaseItem,
|
||||
[],
|
||||
);
|
||||
if (this.databaseManager.currentDatabaseItem !== undefined) {
|
||||
await this.handleUpgradeDatabasesInternal(progress, token, [
|
||||
this.databaseManager.currentDatabaseItem,
|
||||
]);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Upgrading current database",
|
||||
@@ -527,16 +544,14 @@ export class DatabaseUI extends DisposableObject {
|
||||
}
|
||||
|
||||
private async handleUpgradeDatabase(
|
||||
databaseItem: DatabaseItem | undefined,
|
||||
multiSelect: DatabaseItem[] | undefined,
|
||||
databaseItems: DatabaseItem[],
|
||||
): Promise<void> {
|
||||
return withProgress(
|
||||
async (progress, token) => {
|
||||
return await this.handleUpgradeDatabaseInternal(
|
||||
return await this.handleUpgradeDatabasesInternal(
|
||||
progress,
|
||||
token,
|
||||
databaseItem,
|
||||
multiSelect,
|
||||
databaseItems,
|
||||
);
|
||||
},
|
||||
{
|
||||
@@ -546,46 +561,37 @@ export class DatabaseUI extends DisposableObject {
|
||||
);
|
||||
}
|
||||
|
||||
private async handleUpgradeDatabaseInternal(
|
||||
private async handleUpgradeDatabasesInternal(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
databaseItem: DatabaseItem | undefined,
|
||||
multiSelect: DatabaseItem[] | undefined,
|
||||
databaseItems: DatabaseItem[],
|
||||
): Promise<void> {
|
||||
if (multiSelect?.length) {
|
||||
await Promise.all(
|
||||
multiSelect.map((dbItem) =>
|
||||
this.handleUpgradeDatabaseInternal(progress, token, dbItem, []),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (this.queryServer === undefined) {
|
||||
throw new Error(
|
||||
"Received request to upgrade database, but there is no running query server.",
|
||||
);
|
||||
}
|
||||
if (databaseItem === undefined) {
|
||||
throw new Error(
|
||||
"Received request to upgrade database, but no database was provided.",
|
||||
);
|
||||
}
|
||||
if (databaseItem.contents === undefined) {
|
||||
throw new Error(
|
||||
"Received request to upgrade database, but database contents could not be found.",
|
||||
);
|
||||
}
|
||||
if (databaseItem.contents.dbSchemeUri === undefined) {
|
||||
throw new Error(
|
||||
"Received request to upgrade database, but database has no schema.",
|
||||
);
|
||||
}
|
||||
await Promise.all(
|
||||
databaseItems.map(async (databaseItem) => {
|
||||
if (this.queryServer === undefined) {
|
||||
throw new Error(
|
||||
"Received request to upgrade database, but there is no running query server.",
|
||||
);
|
||||
}
|
||||
if (databaseItem.contents === undefined) {
|
||||
throw new Error(
|
||||
"Received request to upgrade database, but database contents could not be found.",
|
||||
);
|
||||
}
|
||||
if (databaseItem.contents.dbSchemeUri === undefined) {
|
||||
throw new Error(
|
||||
"Received request to upgrade database, but database has no schema.",
|
||||
);
|
||||
}
|
||||
|
||||
// Search for upgrade scripts in any workspace folders available
|
||||
// Search for upgrade scripts in any workspace folders available
|
||||
|
||||
await this.queryServer.upgradeDatabaseExplicit(
|
||||
databaseItem,
|
||||
progress,
|
||||
token,
|
||||
await this.queryServer.upgradeDatabaseExplicit(
|
||||
databaseItem,
|
||||
progress,
|
||||
token,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -630,7 +636,7 @@ export class DatabaseUI extends DisposableObject {
|
||||
this.queryServer?.cliServer,
|
||||
);
|
||||
} else {
|
||||
await this.setCurrentDatabase(progress, token, uri);
|
||||
await this.databaseManager.openDatabase(progress, token, uri);
|
||||
}
|
||||
} catch (e) {
|
||||
// rethrow and let this be handled by default error handling.
|
||||
@@ -648,24 +654,15 @@ export class DatabaseUI extends DisposableObject {
|
||||
}
|
||||
|
||||
private async handleRemoveDatabase(
|
||||
databaseItem: DatabaseItem,
|
||||
multiSelect: DatabaseItem[] | undefined,
|
||||
databaseItems: DatabaseItem[],
|
||||
): Promise<void> {
|
||||
return withProgress(
|
||||
async (progress, token) => {
|
||||
if (multiSelect?.length) {
|
||||
await Promise.all(
|
||||
multiSelect.map((dbItem) =>
|
||||
this.databaseManager.removeDatabaseItem(progress, token, dbItem),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await this.databaseManager.removeDatabaseItem(
|
||||
progress,
|
||||
token,
|
||||
databaseItem,
|
||||
);
|
||||
}
|
||||
await Promise.all(
|
||||
databaseItems.map((dbItem) =>
|
||||
this.databaseManager.removeDatabaseItem(progress, token, dbItem),
|
||||
),
|
||||
);
|
||||
},
|
||||
{
|
||||
title: "Removing database",
|
||||
@@ -676,10 +673,7 @@ export class DatabaseUI extends DisposableObject {
|
||||
|
||||
private async handleRenameDatabase(
|
||||
databaseItem: DatabaseItem,
|
||||
multiSelect: DatabaseItem[] | undefined,
|
||||
): Promise<void> {
|
||||
this.assertSingleDatabase(multiSelect);
|
||||
|
||||
const newName = await window.showInputBox({
|
||||
prompt: "Choose new database name",
|
||||
value: databaseItem.name,
|
||||
@@ -690,17 +684,10 @@ export class DatabaseUI extends DisposableObject {
|
||||
}
|
||||
}
|
||||
|
||||
private async handleOpenFolder(
|
||||
databaseItem: DatabaseItem,
|
||||
multiSelect: DatabaseItem[] | undefined,
|
||||
): Promise<void> {
|
||||
if (multiSelect?.length) {
|
||||
await Promise.all(
|
||||
multiSelect.map((dbItem) => env.openExternal(dbItem.databaseUri)),
|
||||
);
|
||||
} else {
|
||||
await env.openExternal(databaseItem.databaseUri);
|
||||
}
|
||||
private async handleOpenFolder(databaseItems: DatabaseItem[]): Promise<void> {
|
||||
await Promise.all(
|
||||
databaseItems.map((dbItem) => env.openExternal(dbItem.databaseUri)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -708,16 +695,9 @@ export class DatabaseUI extends DisposableObject {
|
||||
* When a database is first added in the "Databases" view, its source folder is added to the workspace.
|
||||
* If the source folder is removed from the workspace for some reason, we want to be able to re-add it if need be.
|
||||
*/
|
||||
private async handleAddSource(
|
||||
databaseItem: DatabaseItem,
|
||||
multiSelect: DatabaseItem[] | undefined,
|
||||
): Promise<void> {
|
||||
if (multiSelect?.length) {
|
||||
for (const dbItem of multiSelect) {
|
||||
await this.databaseManager.addDatabaseSourceArchiveFolder(dbItem);
|
||||
}
|
||||
} else {
|
||||
await this.databaseManager.addDatabaseSourceArchiveFolder(databaseItem);
|
||||
private async handleAddSource(databaseItems: DatabaseItem[]): Promise<void> {
|
||||
for (const dbItem of databaseItems) {
|
||||
await this.databaseManager.addDatabaseSourceArchiveFolder(dbItem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -752,24 +732,6 @@ export class DatabaseUI extends DisposableObject {
|
||||
return this.databaseManager.currentDatabaseItem;
|
||||
}
|
||||
|
||||
private async setCurrentDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
uri: Uri,
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
let databaseItem = this.databaseManager.findDatabaseItem(uri);
|
||||
if (databaseItem === undefined) {
|
||||
databaseItem = await this.databaseManager.openDatabase(
|
||||
progress,
|
||||
token,
|
||||
uri,
|
||||
);
|
||||
}
|
||||
await this.databaseManager.setCurrentDatabaseItem(databaseItem);
|
||||
|
||||
return databaseItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the user for a database directory. Returns the chosen database, or `undefined` if the
|
||||
* operation was canceled.
|
||||
@@ -789,7 +751,11 @@ export class DatabaseUI extends DisposableObject {
|
||||
if (byFolder) {
|
||||
const fixedUri = await this.fixDbUri(uri);
|
||||
// we are selecting a database folder
|
||||
return await this.setCurrentDatabase(progress, token, fixedUri);
|
||||
return await this.databaseManager.openDatabase(
|
||||
progress,
|
||||
token,
|
||||
fixedUri,
|
||||
);
|
||||
} else {
|
||||
// we are selecting a database archive. Must unzip into a workspace-controlled area
|
||||
// before importing.
|
||||
@@ -834,13 +800,4 @@ export class DatabaseUI extends DisposableObject {
|
||||
}
|
||||
return Uri.file(dbPath);
|
||||
}
|
||||
|
||||
private assertSingleDatabase(
|
||||
multiSelect: DatabaseItem[] = [],
|
||||
message = "Please select a single database.",
|
||||
) {
|
||||
if (multiSelect.length > 1) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { pathExists, stat, remove } from "fs-extra";
|
||||
import { glob } from "glob";
|
||||
import { join, basename, resolve, relative, dirname, extname } from "path";
|
||||
import * as vscode from "vscode";
|
||||
import * as cli from "./cli";
|
||||
import * as cli from "../cli";
|
||||
import { ExtensionContext } from "vscode";
|
||||
import {
|
||||
showAndLogWarningMessage,
|
||||
@@ -12,24 +12,24 @@ import {
|
||||
isFolderAlreadyInWorkspace,
|
||||
showBinaryChoiceDialog,
|
||||
getFirstWorkspaceFolder,
|
||||
} from "./helpers";
|
||||
import { ProgressCallback, withProgress } from "./progress";
|
||||
} from "../helpers";
|
||||
import { ProgressCallback, withProgress } from "../progress";
|
||||
import {
|
||||
zipArchiveScheme,
|
||||
encodeArchiveBasePath,
|
||||
decodeSourceArchiveUri,
|
||||
encodeSourceArchiveUri,
|
||||
} from "./archive-filesystem-provider";
|
||||
import { DisposableObject } from "./pure/disposable-object";
|
||||
import { Logger, extLogger } from "./common";
|
||||
import { asError, getErrorMessage } from "./pure/helpers-pure";
|
||||
import { QueryRunner } from "./queryRunner";
|
||||
import { pathsEqual } from "./pure/files";
|
||||
import { redactableError } from "./pure/errors";
|
||||
import { isCodespacesTemplate } from "./config";
|
||||
import { QlPackGenerator } from "./qlpack-generator";
|
||||
import { QueryLanguage } from "./common/query-language";
|
||||
import { App } from "./common/app";
|
||||
} from "../archive-filesystem-provider";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { Logger, extLogger } from "../common";
|
||||
import { asError, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { pathsEqual } from "../pure/files";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { isCodespacesTemplate } from "../config";
|
||||
import { QlPackGenerator } from "../qlpack-generator";
|
||||
import { QueryLanguage } from "../common/query-language";
|
||||
import { App } from "../common/app";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
/**
|
||||
@@ -621,6 +621,7 @@ export class DatabaseManager extends DisposableObject {
|
||||
progress: ProgressCallback,
|
||||
token: vscode.CancellationToken,
|
||||
uri: vscode.Uri,
|
||||
makeSelected = false,
|
||||
displayName?: string,
|
||||
isTutorialDatabase?: boolean,
|
||||
): Promise<DatabaseItem> {
|
||||
@@ -629,6 +630,7 @@ export class DatabaseManager extends DisposableObject {
|
||||
return await this.addExistingDatabaseItem(
|
||||
databaseItem,
|
||||
progress,
|
||||
makeSelected,
|
||||
token,
|
||||
isTutorialDatabase,
|
||||
);
|
||||
@@ -643,6 +645,7 @@ export class DatabaseManager extends DisposableObject {
|
||||
public async addExistingDatabaseItem(
|
||||
databaseItem: DatabaseItem,
|
||||
progress: ProgressCallback,
|
||||
makeSelected = true,
|
||||
token: vscode.CancellationToken,
|
||||
isTutorialDatabase?: boolean,
|
||||
): Promise<DatabaseItem> {
|
||||
@@ -652,6 +655,9 @@ export class DatabaseManager extends DisposableObject {
|
||||
}
|
||||
|
||||
await this.addDatabaseItem(progress, token, databaseItem);
|
||||
if (makeSelected) {
|
||||
await this.setCurrentDatabaseItem(databaseItem);
|
||||
}
|
||||
await this.addDatabaseSourceArchiveFolder(databaseItem);
|
||||
|
||||
if (isCodespacesTemplate() && !isTutorialDatabase) {
|
||||
@@ -14,7 +14,7 @@ import { Disposable } from "vscode";
|
||||
import { CancellationTokenSource } from "vscode-jsonrpc";
|
||||
import { BaseLogger, LogOptions, queryServerLogger } from "../common";
|
||||
import { QueryResultType } from "../pure/new-messages";
|
||||
import { CoreQueryResults, CoreQueryRun, QueryRunner } from "../queryRunner";
|
||||
import { CoreQueryResults, CoreQueryRun, QueryRunner } from "../query-server";
|
||||
import * as CodeQLProtocol from "./debug-protocol";
|
||||
import { QuickEvalContext } from "../run-queries-shared";
|
||||
import { getErrorMessage } from "../pure/helpers-pure";
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { isCanary } from "../config";
|
||||
import { LocalQueries } from "../local-queries";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { QLDebugConfigurationProvider } from "./debug-configuration";
|
||||
import { QLDebugSession } from "./debug-session";
|
||||
|
||||
|
||||
@@ -8,10 +8,9 @@ import {
|
||||
CancellationTokenSource,
|
||||
} from "vscode";
|
||||
import { DebuggerCommands } from "../common/commands";
|
||||
import { DatabaseManager } from "../local-databases";
|
||||
import { LocalQueries, LocalQueryRun } from "../local-queries";
|
||||
import { DatabaseManager } from "../databases/local-databases";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { CoreQueryResults } from "../queryRunner";
|
||||
import { CoreQueryResults } from "../query-server";
|
||||
import {
|
||||
getQuickEvalContext,
|
||||
QueryOutputDir,
|
||||
@@ -20,6 +19,7 @@ import {
|
||||
import { QLResolvedDebugConfiguration } from "./debug-configuration";
|
||||
import * as CodeQLProtocol from "./debug-protocol";
|
||||
import { App } from "../common/app";
|
||||
import { LocalQueryRun, LocalQueries } from "../local-queries";
|
||||
|
||||
/**
|
||||
* Listens to messages passing between VS Code and the debug adapter, so that we can supplement the
|
||||
|
||||
@@ -20,13 +20,10 @@ import { dirSync } from "tmp-promise";
|
||||
import { testExplorerExtensionId, TestHub } from "vscode-test-adapter-api";
|
||||
import { lt, parse } from "semver";
|
||||
import { watch } from "chokidar";
|
||||
|
||||
import { AstViewer } from "./astViewer";
|
||||
import {
|
||||
activate as archiveFilesystemProvider_activate,
|
||||
zipArchiveScheme,
|
||||
} from "./archive-filesystem-provider";
|
||||
import QuickEvalCodeLensProvider from "./quickEvalCodeLensProvider";
|
||||
import { CodeQLCliServer } from "./cli";
|
||||
import {
|
||||
CliConfigListener,
|
||||
@@ -36,15 +33,18 @@ import {
|
||||
QueryHistoryConfigListener,
|
||||
QueryServerConfigListener,
|
||||
} from "./config";
|
||||
import { install } from "./languageSupport";
|
||||
import { DatabaseManager } from "./local-databases";
|
||||
import { DatabaseUI } from "./local-databases-ui";
|
||||
import {
|
||||
AstViewer,
|
||||
install,
|
||||
spawnIdeServer,
|
||||
getQueryEditorCommands,
|
||||
TemplatePrintAstProvider,
|
||||
TemplatePrintCfgProvider,
|
||||
TemplateQueryDefinitionProvider,
|
||||
TemplateQueryReferenceProvider,
|
||||
} from "./contextual/templateProvider";
|
||||
} from "./language-support";
|
||||
import { DatabaseManager } from "./databases/local-databases";
|
||||
import { DatabaseUI } from "./databases/local-databases-ui";
|
||||
import {
|
||||
DEFAULT_DISTRIBUTION_VERSION_RANGE,
|
||||
DistributionKind,
|
||||
@@ -72,7 +72,6 @@ import {
|
||||
getErrorMessage,
|
||||
getErrorStack,
|
||||
} from "./pure/helpers-pure";
|
||||
import { spawnIdeServer } from "./ide-server";
|
||||
import { ResultsView } from "./interface";
|
||||
import { WebviewReveal } from "./interface-utils";
|
||||
import {
|
||||
@@ -83,8 +82,10 @@ import {
|
||||
} from "./common";
|
||||
import { QueryHistoryManager } from "./query-history/query-history-manager";
|
||||
import { CompletedLocalQueryInfo } from "./query-results";
|
||||
import { QueryServerClient as LegacyQueryServerClient } from "./legacy-query-server/queryserver-client";
|
||||
import { QueryServerClient } from "./query-server/queryserver-client";
|
||||
import {
|
||||
LegacyQueryRunner,
|
||||
QueryServerClient as LegacyQueryServerClient,
|
||||
} from "./query-server/legacy";
|
||||
import { QLTestAdapterFactory } from "./test-adapter";
|
||||
import { TestUIService } from "./test-ui";
|
||||
import { CompareView } from "./compare/compare-view";
|
||||
@@ -93,13 +94,10 @@ import { ProgressCallback, withProgress } from "./progress";
|
||||
import { CodeQlStatusBarHandler } from "./status-bar";
|
||||
import { getPackagingCommands } from "./packaging";
|
||||
import { HistoryItemLabelProvider } from "./query-history/history-item-label-provider";
|
||||
import { EvalLogViewer } from "./eval-log-viewer";
|
||||
import { EvalLogViewer } from "./query-evaluation-logging";
|
||||
import { SummaryLanguageSupport } from "./log-insights/summary-language-support";
|
||||
import { JoinOrderScannerProvider } from "./log-insights/join-order";
|
||||
import { LogScannerService } from "./log-insights/log-scanner-service";
|
||||
import { LegacyQueryRunner } from "./legacy-query-server/legacyRunner";
|
||||
import { NewQueryRunner } from "./query-server/query-runner";
|
||||
import { QueryRunner } from "./queryRunner";
|
||||
import { VariantAnalysisView } from "./variant-analysis/variant-analysis-view";
|
||||
import { VariantAnalysisViewSerializer } from "./variant-analysis/variant-analysis-view-serializer";
|
||||
import { VariantAnalysisManager } from "./variant-analysis/variant-analysis-manager";
|
||||
@@ -117,9 +115,8 @@ import {
|
||||
PreActivationCommands,
|
||||
QueryServerCommands,
|
||||
} from "./common/commands";
|
||||
import { LocalQueries } from "./local-queries";
|
||||
import { getAstCfgCommands } from "./ast-cfg-commands";
|
||||
import { getQueryEditorCommands } from "./query-editor";
|
||||
import { LocalQueries, QuickEvalCodeLensProvider } from "./local-queries";
|
||||
import { getAstCfgCommands } from "./language-support/ast-viewer/ast-cfg-commands";
|
||||
import { App } from "./common/app";
|
||||
import { registerCommandWithErrorHandling } from "./common/vscode/commands";
|
||||
import { DebuggerUI } from "./debugger/debugger-ui";
|
||||
@@ -127,6 +124,7 @@ import { DataExtensionsEditorModule } from "./data-extensions-editor/data-extens
|
||||
import { TestManager } from "./test-manager";
|
||||
import { TestRunner } from "./test-runner";
|
||||
import { TestManagerBase } from "./test-manager-base";
|
||||
import { NewQueryRunner, QueryRunner, QueryServerClient } from "./query-server";
|
||||
|
||||
/**
|
||||
* extension.ts
|
||||
@@ -317,7 +315,7 @@ export async function activate(
|
||||
|
||||
const distributionConfigListener = new DistributionConfigListener();
|
||||
await initializeLogging(ctx);
|
||||
await initializeTelemetry(extension, ctx);
|
||||
const telemetryListener = await initializeTelemetry(extension, ctx);
|
||||
addUnhandledRejectionListener();
|
||||
install();
|
||||
|
||||
@@ -406,6 +404,9 @@ export async function activate(
|
||||
variantAnalysisViewSerializer.onExtensionLoaded(
|
||||
codeQlExtension.variantAnalysisManager,
|
||||
);
|
||||
codeQlExtension.cliServer.addVersionChangedListener((ver) => {
|
||||
telemetryListener.cliVersion = ver;
|
||||
});
|
||||
}
|
||||
|
||||
return codeQlExtension;
|
||||
@@ -704,9 +705,14 @@ async function activateWithInstalledDistribution(
|
||||
for (const glob of PACK_GLOBS) {
|
||||
const fsWatcher = workspace.createFileSystemWatcher(glob);
|
||||
ctx.subscriptions.push(fsWatcher);
|
||||
fsWatcher.onDidChange(async (_uri) => {
|
||||
|
||||
const clearPackCache = async (_uri: Uri) => {
|
||||
await qs.clearPackCache();
|
||||
});
|
||||
};
|
||||
|
||||
fsWatcher.onDidCreate(clearPackCache);
|
||||
fsWatcher.onDidChange(clearPackCache);
|
||||
fsWatcher.onDidDelete(clearPackCache);
|
||||
}
|
||||
|
||||
void extLogger.log("Initializing database manager.");
|
||||
|
||||
@@ -249,15 +249,17 @@ export async function showInformationMessageWithAction(
|
||||
return chosenItem === actionItem;
|
||||
}
|
||||
|
||||
/** Returns true if the specified workspace folder is on the file system. */
|
||||
export function isWorkspaceFolderOnDisk(
|
||||
workspaceFolder: WorkspaceFolder,
|
||||
): boolean {
|
||||
return workspaceFolder.uri.scheme === "file";
|
||||
}
|
||||
|
||||
/** Gets all active workspace folders that are on the filesystem. */
|
||||
export function getOnDiskWorkspaceFoldersObjects() {
|
||||
const workspaceFolders = workspace.workspaceFolders || [];
|
||||
const diskWorkspaceFolders: WorkspaceFolder[] = [];
|
||||
for (const workspaceFolder of workspaceFolders) {
|
||||
if (workspaceFolder.uri.scheme === "file")
|
||||
diskWorkspaceFolders.push(workspaceFolder);
|
||||
}
|
||||
return diskWorkspaceFolders;
|
||||
const workspaceFolders = workspace.workspaceFolders ?? [];
|
||||
return workspaceFolders.filter(isWorkspaceFolderOnDisk);
|
||||
}
|
||||
|
||||
/** Gets all active workspace folders that are on the filesystem. */
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
ThemeColor,
|
||||
} from "vscode";
|
||||
import { tryGetResolvableLocation, isLineColumnLoc } from "./pure/bqrs-utils";
|
||||
import { DatabaseItem, DatabaseManager } from "./local-databases";
|
||||
import { DatabaseItem, DatabaseManager } from "./databases/local-databases";
|
||||
import { ViewSourceFileMsg } from "./pure/interface-types";
|
||||
import { Logger } from "./common";
|
||||
import {
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
DatabaseEventKind,
|
||||
DatabaseItem,
|
||||
DatabaseManager,
|
||||
} from "./local-databases";
|
||||
} from "./databases/local-databases";
|
||||
import { showAndLogExceptionWithTelemetry } from "./helpers";
|
||||
import {
|
||||
asError,
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { DecodedBqrsChunk, BqrsId, EntityValue } from "../pure/bqrs-cli-types";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { ChildAstItem, AstItem } from "../astViewer";
|
||||
import fileRangeFromURI from "./fileRangeFromURI";
|
||||
import { CodeQLCliServer } from "../../cli";
|
||||
import {
|
||||
DecodedBqrsChunk,
|
||||
BqrsId,
|
||||
EntityValue,
|
||||
} from "../../pure/bqrs-cli-types";
|
||||
import { DatabaseItem } from "../../databases/local-databases";
|
||||
import { ChildAstItem, AstItem } from "./ast-viewer";
|
||||
import { Uri } from "vscode";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
import { QueryOutputDir } from "../../run-queries-shared";
|
||||
import { fileRangeFromURI } from "../contextual/file-range-from-uri";
|
||||
|
||||
/**
|
||||
* A class that wraps a tree of QL results from a query that
|
||||
* has an @kind of graph
|
||||
*/
|
||||
export default class AstBuilder {
|
||||
export class AstBuilder {
|
||||
private roots: AstItem[] | undefined;
|
||||
private bqrsPath: string;
|
||||
constructor(
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Uri, window } from "vscode";
|
||||
import { withProgress } from "./progress";
|
||||
import { AstViewer } from "./astViewer";
|
||||
import { withProgress } from "../../progress";
|
||||
import { AstViewer } from "./ast-viewer";
|
||||
import { AstCfgCommands } from "../../common/commands";
|
||||
import { LocalQueries } from "../../local-queries";
|
||||
import {
|
||||
TemplatePrintAstProvider,
|
||||
TemplatePrintCfgProvider,
|
||||
} from "./contextual/templateProvider";
|
||||
import { AstCfgCommands } from "./common/commands";
|
||||
import { LocalQueries } from "./local-queries";
|
||||
} from "../contextual/template-provider";
|
||||
|
||||
type AstCfgOptions = {
|
||||
localQueries: LocalQueries;
|
||||
@@ -15,19 +15,19 @@ import {
|
||||
} from "vscode";
|
||||
import { basename } from "path";
|
||||
|
||||
import { DatabaseItem } from "./local-databases";
|
||||
import { UrlValue, BqrsId } from "./pure/bqrs-cli-types";
|
||||
import { showLocation } from "./interface-utils";
|
||||
import { DatabaseItem } from "../../databases/local-databases";
|
||||
import { UrlValue, BqrsId } from "../../pure/bqrs-cli-types";
|
||||
import { showLocation } from "../../interface-utils";
|
||||
import {
|
||||
isStringLoc,
|
||||
isWholeFileLoc,
|
||||
isLineColumnLoc,
|
||||
} from "./pure/bqrs-utils";
|
||||
import { DisposableObject } from "./pure/disposable-object";
|
||||
import { showAndLogExceptionWithTelemetry } from "./helpers";
|
||||
import { asError, getErrorMessage } from "./pure/helpers-pure";
|
||||
import { redactableError } from "./pure/errors";
|
||||
import { AstViewerCommands } from "./common/commands";
|
||||
} from "../../pure/bqrs-utils";
|
||||
import { DisposableObject } from "../../pure/disposable-object";
|
||||
import { showAndLogExceptionWithTelemetry } from "../../helpers";
|
||||
import { asError, getErrorMessage } from "../../pure/helpers-pure";
|
||||
import { redactableError } from "../../pure/errors";
|
||||
import { AstViewerCommands } from "../../common/commands";
|
||||
|
||||
export interface AstItem {
|
||||
id: BqrsId;
|
||||
@@ -1,10 +1,10 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { UrlValue, LineColumnLocation } from "../pure/bqrs-cli-types";
|
||||
import { isEmptyPath } from "../pure/bqrs-utils";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { UrlValue, LineColumnLocation } from "../../pure/bqrs-cli-types";
|
||||
import { isEmptyPath } from "../../pure/bqrs-utils";
|
||||
import { DatabaseItem } from "../../databases/local-databases";
|
||||
|
||||
export default function fileRangeFromURI(
|
||||
export function fileRangeFromURI(
|
||||
uri: UrlValue | undefined,
|
||||
db: DatabaseItem,
|
||||
): vscode.Location | undefined {
|
||||
@@ -1,27 +1,27 @@
|
||||
import {
|
||||
decodeSourceArchiveUri,
|
||||
encodeArchiveBasePath,
|
||||
} from "../archive-filesystem-provider";
|
||||
} from "../../archive-filesystem-provider";
|
||||
import {
|
||||
ColumnKindCode,
|
||||
EntityValue,
|
||||
getResultSetSchema,
|
||||
ResultSetSchema,
|
||||
} from "../pure/bqrs-cli-types";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { DatabaseManager, DatabaseItem } from "../local-databases";
|
||||
import fileRangeFromURI from "./fileRangeFromURI";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import { KeyType } from "./keyType";
|
||||
} from "../../pure/bqrs-cli-types";
|
||||
import { CodeQLCliServer } from "../../cli";
|
||||
import { DatabaseManager, DatabaseItem } from "../../databases/local-databases";
|
||||
import { ProgressCallback } from "../../progress";
|
||||
import { KeyType } from "./key-type";
|
||||
import {
|
||||
qlpackOfDatabase,
|
||||
resolveQueries,
|
||||
runContextualQuery,
|
||||
} from "./queryResolver";
|
||||
} from "./query-resolver";
|
||||
import { CancellationToken, LocationLink, Uri } from "vscode";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { QueryResultType } from "../pure/new-messages";
|
||||
import { QueryOutputDir } from "../../run-queries-shared";
|
||||
import { QueryRunner } from "../../query-server";
|
||||
import { QueryResultType } from "../../pure/new-messages";
|
||||
import { fileRangeFromURI } from "./file-range-from-uri";
|
||||
|
||||
export const SELECT_QUERY_NAME = "#select";
|
||||
export const TEMPLATE_NAME = "selectedSourceFile";
|
||||
@@ -9,16 +9,21 @@ import {
|
||||
getOnDiskWorkspaceFolders,
|
||||
QlPacksForLanguage,
|
||||
showAndLogExceptionWithTelemetry,
|
||||
} from "../helpers";
|
||||
import { KeyType, kindOfKeyType, nameOfKeyType, tagOfKeyType } from "./keyType";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { extLogger, TeeLogger } from "../common";
|
||||
} from "../../helpers";
|
||||
import {
|
||||
KeyType,
|
||||
kindOfKeyType,
|
||||
nameOfKeyType,
|
||||
tagOfKeyType,
|
||||
} from "./key-type";
|
||||
import { CodeQLCliServer } from "../../cli";
|
||||
import { DatabaseItem } from "../../databases/local-databases";
|
||||
import { extLogger, TeeLogger } from "../../common";
|
||||
import { CancellationToken } from "vscode";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../queryRunner";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { QLPACK_FILENAMES } from "../pure/ql";
|
||||
import { ProgressCallback } from "../../progress";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../../query-server";
|
||||
import { redactableError } from "../../pure/errors";
|
||||
import { QLPACK_FILENAMES } from "../../pure/ql";
|
||||
|
||||
export async function qlpackOfDatabase(
|
||||
cli: Pick<CodeQLCliServer, "resolveQlpacks">,
|
||||
@@ -14,25 +14,25 @@ import {
|
||||
decodeSourceArchiveUri,
|
||||
encodeArchiveBasePath,
|
||||
zipArchiveScheme,
|
||||
} from "../archive-filesystem-provider";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { DatabaseManager } from "../local-databases";
|
||||
import { CachedOperation } from "../helpers";
|
||||
import { ProgressCallback, withProgress } from "../progress";
|
||||
import AstBuilder from "./astBuilder";
|
||||
import { KeyType } from "./keyType";
|
||||
} from "../../archive-filesystem-provider";
|
||||
import { CodeQLCliServer } from "../../cli";
|
||||
import { DatabaseManager } from "../../databases/local-databases";
|
||||
import { CachedOperation } from "../../helpers";
|
||||
import { ProgressCallback, withProgress } from "../../progress";
|
||||
import { KeyType } from "./key-type";
|
||||
import {
|
||||
FullLocationLink,
|
||||
getLocationsForUriString,
|
||||
TEMPLATE_NAME,
|
||||
} from "./locationFinder";
|
||||
} from "./location-finder";
|
||||
import {
|
||||
qlpackOfDatabase,
|
||||
resolveQueries,
|
||||
runContextualQuery,
|
||||
} from "./queryResolver";
|
||||
import { isCanary, NO_CACHE_AST_VIEWER } from "../config";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../queryRunner";
|
||||
} from "./query-resolver";
|
||||
import { isCanary, NO_CACHE_AST_VIEWER } from "../../config";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../../query-server";
|
||||
import { AstBuilder } from "../ast-viewer/ast-builder";
|
||||
|
||||
/**
|
||||
* Runs templated CodeQL queries to find definitions in
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ProgressLocation, window } from "vscode";
|
||||
import { StreamInfo } from "vscode-languageclient/node";
|
||||
import { shouldDebugIdeServer, spawnServer } from "./cli";
|
||||
import { QueryServerConfig } from "./config";
|
||||
import { ideServerLogger } from "./common";
|
||||
import { shouldDebugIdeServer, spawnServer } from "../cli";
|
||||
import { QueryServerConfig } from "../config";
|
||||
import { ideServerLogger } from "../common";
|
||||
|
||||
/**
|
||||
* Managing the language server for CodeQL.
|
||||
10
extensions/ql-vscode/src/language-support/index.ts
Normal file
10
extensions/ql-vscode/src/language-support/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export * from "./ast-viewer/ast-builder";
|
||||
export * from "./ast-viewer/ast-viewer";
|
||||
export * from "./contextual/file-range-from-uri";
|
||||
export * from "./contextual/key-type";
|
||||
export * from "./contextual/location-finder";
|
||||
export * from "./contextual/query-resolver";
|
||||
export * from "./contextual/template-provider";
|
||||
export * from "./ide-server";
|
||||
export * from "./language-support";
|
||||
export * from "./query-editor";
|
||||
@@ -12,7 +12,7 @@ import { languages, IndentAction, OnEnterRule } from "vscode";
|
||||
*/
|
||||
export function install() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const langConfig = require("../language-configuration.json");
|
||||
const langConfig = require("../../language-configuration.json");
|
||||
// setLanguageConfiguration requires a regexp for the wordpattern, not a string
|
||||
langConfig.wordPattern = new RegExp(langConfig.wordPattern);
|
||||
langConfig.onEnterRules = onEnterRules;
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Uri, window } from "vscode";
|
||||
import { CodeQLCliServer } from "./cli";
|
||||
import { QueryRunner } from "./queryRunner";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { basename, join } from "path";
|
||||
import { getErrorMessage } from "./pure/helpers-pure";
|
||||
import { redactableError } from "./pure/errors";
|
||||
import { showAndLogExceptionWithTelemetry } from "./helpers";
|
||||
import { AppCommandManager, QueryEditorCommands } from "./common/commands";
|
||||
import { getErrorMessage } from "../pure/helpers-pure";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { showAndLogExceptionWithTelemetry } from "../helpers";
|
||||
import { AppCommandManager, QueryEditorCommands } from "../common/commands";
|
||||
|
||||
type QueryEditorOptions = {
|
||||
commandManager: AppCommandManager;
|
||||
4
extensions/ql-vscode/src/local-queries/index.ts
Normal file
4
extensions/ql-vscode/src/local-queries/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./local-queries";
|
||||
export * from "./local-query-run";
|
||||
export * from "./quick-eval-code-lens-provider";
|
||||
export * from "./quick-query";
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ProgressCallback, ProgressUpdate, withProgress } from "./progress";
|
||||
import { ProgressCallback, ProgressUpdate, withProgress } from "../progress";
|
||||
import {
|
||||
CancellationToken,
|
||||
CancellationTokenSource,
|
||||
@@ -8,76 +8,47 @@ import {
|
||||
window,
|
||||
workspace,
|
||||
} from "vscode";
|
||||
import { BaseLogger, extLogger, Logger, TeeLogger } from "./common";
|
||||
import { isCanary, MAX_QUERIES } from "./config";
|
||||
import { gatherQlFiles } from "./pure/files";
|
||||
import { extLogger, TeeLogger } from "../common";
|
||||
import { isCanary, MAX_QUERIES } from "../config";
|
||||
import { gatherQlFiles } from "../pure/files";
|
||||
import { basename } from "path";
|
||||
import {
|
||||
createTimestampFile,
|
||||
findLanguage,
|
||||
getOnDiskWorkspaceFolders,
|
||||
showAndLogErrorMessage,
|
||||
showAndLogExceptionWithTelemetry,
|
||||
showAndLogWarningMessage,
|
||||
showBinaryChoiceDialog,
|
||||
tryGetQueryMetadata,
|
||||
} from "./helpers";
|
||||
} from "../helpers";
|
||||
import { displayQuickQuery } from "./quick-query";
|
||||
import {
|
||||
CoreCompletedQuery,
|
||||
CoreQueryResults,
|
||||
QueryRunner,
|
||||
} from "./queryRunner";
|
||||
import { QueryHistoryManager } from "./query-history/query-history-manager";
|
||||
import { DatabaseUI } from "./local-databases-ui";
|
||||
import { ResultsView } from "./interface";
|
||||
import { DatabaseItem, DatabaseManager } from "./local-databases";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../query-server";
|
||||
import { QueryHistoryManager } from "../query-history/query-history-manager";
|
||||
import { DatabaseUI } from "../databases/local-databases-ui";
|
||||
import { ResultsView } from "../interface";
|
||||
import { DatabaseItem, DatabaseManager } from "../databases/local-databases";
|
||||
import {
|
||||
createInitialQueryInfo,
|
||||
EvaluatorLogPaths,
|
||||
generateEvalLogSummaries,
|
||||
getQuickEvalContext,
|
||||
logEndSummary,
|
||||
promptUserToSaveChanges,
|
||||
QueryEvaluationInfo,
|
||||
QueryOutputDir,
|
||||
QueryWithResults,
|
||||
SelectedQuery,
|
||||
validateQueryUri,
|
||||
} from "./run-queries-shared";
|
||||
import { CompletedLocalQueryInfo, LocalQueryInfo } from "./query-results";
|
||||
import { WebviewReveal } from "./interface-utils";
|
||||
import { asError, getErrorMessage } from "./pure/helpers-pure";
|
||||
import { CodeQLCliServer } from "./cli";
|
||||
import { LocalQueryCommands } from "./common/commands";
|
||||
import { App } from "./common/app";
|
||||
import { DisposableObject } from "./pure/disposable-object";
|
||||
import { QueryResultType } from "./pure/new-messages";
|
||||
import { redactableError } from "./pure/errors";
|
||||
import { SkeletonQueryWizard } from "./skeleton-query-wizard";
|
||||
} from "../run-queries-shared";
|
||||
import { CompletedLocalQueryInfo, LocalQueryInfo } from "../query-results";
|
||||
import { WebviewReveal } from "../interface-utils";
|
||||
import { asError, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { LocalQueryCommands } from "../common/commands";
|
||||
import { App } from "../common/app";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { SkeletonQueryWizard } from "../skeleton-query-wizard";
|
||||
import { LocalQueryRun } from "./local-query-run";
|
||||
import { createMultiSelectionCommand } from "../common/selection-commands";
|
||||
|
||||
interface DatabaseQuickPickItem extends QuickPickItem {
|
||||
databaseItem: DatabaseItem;
|
||||
}
|
||||
|
||||
function formatResultMessage(result: CoreQueryResults): string {
|
||||
switch (result.resultType) {
|
||||
case QueryResultType.CANCELLATION:
|
||||
return `cancelled after ${Math.round(
|
||||
result.evaluationTime / 1000,
|
||||
)} seconds`;
|
||||
case QueryResultType.OOM:
|
||||
return "out of memory";
|
||||
case QueryResultType.SUCCESS:
|
||||
return `finished in ${Math.round(result.evaluationTime / 1000)} seconds`;
|
||||
case QueryResultType.COMPILATION_ERROR:
|
||||
return `compilation failed: ${result.message}`;
|
||||
case QueryResultType.OTHER_ERROR:
|
||||
default:
|
||||
return result.message ? `failed: ${result.message}` : "failed";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If either the query file or the quickeval file is dirty, give the user the chance to save them.
|
||||
*/
|
||||
@@ -97,142 +68,6 @@ async function promptToSaveQueryIfNeeded(query: SelectedQuery): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the evaluation of a local query, including its interactions with the UI.
|
||||
*
|
||||
* The client creates an instance of `LocalQueryRun` when the evaluation starts, and then invokes
|
||||
* the `complete()` function once the query has completed (successfully or otherwise).
|
||||
*
|
||||
* Having the client tell the `LocalQueryRun` when the evaluation is complete, rather than having
|
||||
* the `LocalQueryRun` manage the evaluation itself, may seem a bit clunky. It's done this way
|
||||
* because once we move query evaluation into a Debug Adapter, the debugging UI drives the
|
||||
* evaluation, and we can only respond to events from the debug adapter.
|
||||
*/
|
||||
export class LocalQueryRun {
|
||||
public constructor(
|
||||
private readonly outputDir: QueryOutputDir,
|
||||
private readonly localQueries: LocalQueries,
|
||||
private readonly queryInfo: LocalQueryInfo,
|
||||
private readonly dbItem: DatabaseItem,
|
||||
public readonly logger: Logger, // Public so that other clients, like the debug adapter, know where to send log output
|
||||
private readonly queryHistoryManager: QueryHistoryManager,
|
||||
private readonly cliServer: CodeQLCliServer,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Updates the UI based on the results of the query evaluation. This creates the evaluator log
|
||||
* summaries, updates the query history item for the evaluation with the results and evaluation
|
||||
* time, and displays the results view.
|
||||
*
|
||||
* This function must be called when the evaluation completes, whether the evaluation was
|
||||
* successful or not.
|
||||
* */
|
||||
public async complete(results: CoreQueryResults): Promise<void> {
|
||||
const evalLogPaths = await this.summarizeEvalLog(
|
||||
results.resultType,
|
||||
this.outputDir,
|
||||
this.logger,
|
||||
);
|
||||
if (evalLogPaths !== undefined) {
|
||||
this.queryInfo.setEvaluatorLogPaths(evalLogPaths);
|
||||
}
|
||||
const queryWithResults = await this.getCompletedQueryInfo(results);
|
||||
this.queryHistoryManager.completeQuery(this.queryInfo, queryWithResults);
|
||||
await this.localQueries.showResultsForCompletedQuery(
|
||||
this.queryInfo 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
|
||||
await this.queryHistoryManager.refreshTreeView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI in the case where query evaluation throws an exception.
|
||||
*/
|
||||
public async fail(err: Error): Promise<void> {
|
||||
err.message = `Error running query: ${err.message}`;
|
||||
this.queryInfo.failureReason = err.message;
|
||||
await this.queryHistoryManager.refreshTreeView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate summaries of the structured evaluator log.
|
||||
*/
|
||||
private async summarizeEvalLog(
|
||||
resultType: QueryResultType,
|
||||
outputDir: QueryOutputDir,
|
||||
logger: BaseLogger,
|
||||
): Promise<EvaluatorLogPaths | undefined> {
|
||||
const evalLogPaths = await generateEvalLogSummaries(
|
||||
this.cliServer,
|
||||
outputDir,
|
||||
);
|
||||
if (evalLogPaths !== undefined) {
|
||||
if (evalLogPaths.endSummary !== undefined) {
|
||||
void logEndSummary(evalLogPaths.endSummary, logger); // Logged asynchrnously
|
||||
}
|
||||
} else {
|
||||
// Raw evaluator log was not found. Notify the user, unless we know why it wasn't found.
|
||||
if (resultType === QueryResultType.SUCCESS) {
|
||||
void showAndLogWarningMessage(
|
||||
`Failed to write structured evaluator log to ${outputDir.evalLogPath}.`,
|
||||
);
|
||||
} else {
|
||||
// Don't bother notifying the user if there's no log. For some errors, like compilation
|
||||
// errors, we don't expect a log. For cancellations and OOM errors, whether or not we have
|
||||
// a log depends on how far execution got before termination.
|
||||
}
|
||||
}
|
||||
|
||||
return evalLogPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a `QueryWithResults` containing information about the evaluation of the query and its
|
||||
* result, in the form expected by the query history UI.
|
||||
*/
|
||||
private async getCompletedQueryInfo(
|
||||
results: CoreQueryResults,
|
||||
): Promise<QueryWithResults> {
|
||||
// Read the query metadata if possible, to use in the UI.
|
||||
const metadata = await tryGetQueryMetadata(
|
||||
this.cliServer,
|
||||
this.queryInfo.initialInfo.queryPath,
|
||||
);
|
||||
const query = new QueryEvaluationInfo(
|
||||
this.outputDir.querySaveDir,
|
||||
this.dbItem.databaseUri.fsPath,
|
||||
await this.dbItem.hasMetadataFile(),
|
||||
this.queryInfo.initialInfo.quickEvalPosition,
|
||||
metadata,
|
||||
);
|
||||
|
||||
if (results.resultType !== QueryResultType.SUCCESS) {
|
||||
const message = results.message
|
||||
? redactableError`Failed to run query: ${results.message}`
|
||||
: redactableError`Failed to run query`;
|
||||
void showAndLogExceptionWithTelemetry(message);
|
||||
}
|
||||
const message = formatResultMessage(results);
|
||||
const successful = results.resultType === QueryResultType.SUCCESS;
|
||||
return {
|
||||
query,
|
||||
result: {
|
||||
evaluationTime: results.evaluationTime,
|
||||
queryId: 0,
|
||||
resultType: successful
|
||||
? QueryResultType.SUCCESS
|
||||
: QueryResultType.OTHER_ERROR,
|
||||
runId: 0,
|
||||
message,
|
||||
},
|
||||
message,
|
||||
successful,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class LocalQueries extends DisposableObject {
|
||||
public constructor(
|
||||
private readonly app: App,
|
||||
@@ -255,7 +90,9 @@ export class LocalQueries extends DisposableObject {
|
||||
this.runQueryOnMultipleDatabases.bind(this),
|
||||
"codeQL.runQueryOnMultipleDatabasesContextEditor":
|
||||
this.runQueryOnMultipleDatabases.bind(this),
|
||||
"codeQL.runQueries": this.runQueries.bind(this),
|
||||
"codeQL.runQueries": createMultiSelectionCommand(
|
||||
this.runQueries.bind(this),
|
||||
),
|
||||
"codeQL.quickEval": this.quickEval.bind(this),
|
||||
"codeQL.quickEvalContextEditor": this.quickEval.bind(this),
|
||||
"codeQL.codeLensQuickEval": this.codeLensQuickEval.bind(this),
|
||||
@@ -296,12 +133,12 @@ export class LocalQueries extends DisposableObject {
|
||||
);
|
||||
}
|
||||
|
||||
private async runQueries(_: unknown, multi: Uri[]): Promise<void> {
|
||||
private async runQueries(fileURIs: Uri[]): Promise<void> {
|
||||
await withProgress(
|
||||
async (progress, token) => {
|
||||
const maxQueryCount = MAX_QUERIES.getValue() as number;
|
||||
const [files, dirFound] = await gatherQlFiles(
|
||||
multi.map((uri) => uri.fsPath),
|
||||
fileURIs.map((uri) => uri.fsPath),
|
||||
);
|
||||
if (files.length > maxQueryCount) {
|
||||
throw new Error(
|
||||
177
extensions/ql-vscode/src/local-queries/local-query-run.ts
Normal file
177
extensions/ql-vscode/src/local-queries/local-query-run.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { BaseLogger, Logger } from "../common";
|
||||
import {
|
||||
showAndLogExceptionWithTelemetry,
|
||||
showAndLogWarningMessage,
|
||||
tryGetQueryMetadata,
|
||||
} from "../helpers";
|
||||
import { CoreQueryResults } from "../query-server";
|
||||
import { QueryHistoryManager } from "../query-history/query-history-manager";
|
||||
import { DatabaseItem } from "../databases/local-databases";
|
||||
import {
|
||||
EvaluatorLogPaths,
|
||||
generateEvalLogSummaries,
|
||||
logEndSummary,
|
||||
QueryEvaluationInfo,
|
||||
QueryOutputDir,
|
||||
QueryWithResults,
|
||||
} from "../run-queries-shared";
|
||||
import { CompletedLocalQueryInfo, LocalQueryInfo } from "../query-results";
|
||||
import { WebviewReveal } from "../interface-utils";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { QueryResultType } from "../pure/new-messages";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { LocalQueries } from "./local-queries";
|
||||
|
||||
function formatResultMessage(result: CoreQueryResults): string {
|
||||
switch (result.resultType) {
|
||||
case QueryResultType.CANCELLATION:
|
||||
return `cancelled after ${Math.round(
|
||||
result.evaluationTime / 1000,
|
||||
)} seconds`;
|
||||
case QueryResultType.OOM:
|
||||
return "out of memory";
|
||||
case QueryResultType.SUCCESS:
|
||||
return `finished in ${Math.round(result.evaluationTime / 1000)} seconds`;
|
||||
case QueryResultType.COMPILATION_ERROR:
|
||||
return `compilation failed: ${result.message}`;
|
||||
case QueryResultType.OTHER_ERROR:
|
||||
default:
|
||||
return result.message ? `failed: ${result.message}` : "failed";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the evaluation of a local query, including its interactions with the UI.
|
||||
*
|
||||
* The client creates an instance of `LocalQueryRun` when the evaluation starts, and then invokes
|
||||
* the `complete()` function once the query has completed (successfully or otherwise).
|
||||
*
|
||||
* Having the client tell the `LocalQueryRun` when the evaluation is complete, rather than having
|
||||
* the `LocalQueryRun` manage the evaluation itself, may seem a bit clunky. It's done this way
|
||||
* because once we move query evaluation into a Debug Adapter, the debugging UI drives the
|
||||
* evaluation, and we can only respond to events from the debug adapter.
|
||||
*/
|
||||
export class LocalQueryRun {
|
||||
public constructor(
|
||||
private readonly outputDir: QueryOutputDir,
|
||||
private readonly localQueries: LocalQueries,
|
||||
private readonly queryInfo: LocalQueryInfo,
|
||||
private readonly dbItem: DatabaseItem,
|
||||
public readonly logger: Logger, // Public so that other clients, like the debug adapter, know where to send log output
|
||||
private readonly queryHistoryManager: QueryHistoryManager,
|
||||
private readonly cliServer: CodeQLCliServer,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Updates the UI based on the results of the query evaluation. This creates the evaluator log
|
||||
* summaries, updates the query history item for the evaluation with the results and evaluation
|
||||
* time, and displays the results view.
|
||||
*
|
||||
* This function must be called when the evaluation completes, whether the evaluation was
|
||||
* successful or not.
|
||||
* */
|
||||
public async complete(results: CoreQueryResults): Promise<void> {
|
||||
const evalLogPaths = await this.summarizeEvalLog(
|
||||
results.resultType,
|
||||
this.outputDir,
|
||||
this.logger,
|
||||
);
|
||||
if (evalLogPaths !== undefined) {
|
||||
this.queryInfo.setEvaluatorLogPaths(evalLogPaths);
|
||||
}
|
||||
const queryWithResults = await this.getCompletedQueryInfo(results);
|
||||
this.queryHistoryManager.completeQuery(this.queryInfo, queryWithResults);
|
||||
await this.localQueries.showResultsForCompletedQuery(
|
||||
this.queryInfo 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
|
||||
await this.queryHistoryManager.refreshTreeView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI in the case where query evaluation throws an exception.
|
||||
*/
|
||||
public async fail(err: Error): Promise<void> {
|
||||
err.message = `Error running query: ${err.message}`;
|
||||
this.queryInfo.failureReason = err.message;
|
||||
await this.queryHistoryManager.refreshTreeView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate summaries of the structured evaluator log.
|
||||
*/
|
||||
private async summarizeEvalLog(
|
||||
resultType: QueryResultType,
|
||||
outputDir: QueryOutputDir,
|
||||
logger: BaseLogger,
|
||||
): Promise<EvaluatorLogPaths | undefined> {
|
||||
const evalLogPaths = await generateEvalLogSummaries(
|
||||
this.cliServer,
|
||||
outputDir,
|
||||
);
|
||||
if (evalLogPaths !== undefined) {
|
||||
if (evalLogPaths.endSummary !== undefined) {
|
||||
void logEndSummary(evalLogPaths.endSummary, logger); // Logged asynchrnously
|
||||
}
|
||||
} else {
|
||||
// Raw evaluator log was not found. Notify the user, unless we know why it wasn't found.
|
||||
if (resultType === QueryResultType.SUCCESS) {
|
||||
void showAndLogWarningMessage(
|
||||
`Failed to write structured evaluator log to ${outputDir.evalLogPath}.`,
|
||||
);
|
||||
} else {
|
||||
// Don't bother notifying the user if there's no log. For some errors, like compilation
|
||||
// errors, we don't expect a log. For cancellations and OOM errors, whether or not we have
|
||||
// a log depends on how far execution got before termination.
|
||||
}
|
||||
}
|
||||
|
||||
return evalLogPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a `QueryWithResults` containing information about the evaluation of the query and its
|
||||
* result, in the form expected by the query history UI.
|
||||
*/
|
||||
private async getCompletedQueryInfo(
|
||||
results: CoreQueryResults,
|
||||
): Promise<QueryWithResults> {
|
||||
// Read the query metadata if possible, to use in the UI.
|
||||
const metadata = await tryGetQueryMetadata(
|
||||
this.cliServer,
|
||||
this.queryInfo.initialInfo.queryPath,
|
||||
);
|
||||
const query = new QueryEvaluationInfo(
|
||||
this.outputDir.querySaveDir,
|
||||
this.dbItem.databaseUri.fsPath,
|
||||
await this.dbItem.hasMetadataFile(),
|
||||
this.queryInfo.initialInfo.quickEvalPosition,
|
||||
metadata,
|
||||
);
|
||||
|
||||
if (results.resultType !== QueryResultType.SUCCESS) {
|
||||
const message = results.message
|
||||
? redactableError`Failed to run query: ${results.message}`
|
||||
: redactableError`Failed to run query`;
|
||||
void showAndLogExceptionWithTelemetry(message);
|
||||
}
|
||||
const message = formatResultMessage(results);
|
||||
const successful = results.resultType === QueryResultType.SUCCESS;
|
||||
return {
|
||||
query,
|
||||
result: {
|
||||
evaluationTime: results.evaluationTime,
|
||||
queryId: 0,
|
||||
resultType: successful
|
||||
? QueryResultType.SUCCESS
|
||||
: QueryResultType.OTHER_ERROR,
|
||||
runId: 0,
|
||||
message,
|
||||
},
|
||||
message,
|
||||
successful,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import {
|
||||
Command,
|
||||
Range,
|
||||
} from "vscode";
|
||||
import { isQuickEvalCodelensEnabled } from "./config";
|
||||
import { isQuickEvalCodelensEnabled } from "../config";
|
||||
|
||||
class QuickEvalCodeLensProvider implements CodeLensProvider {
|
||||
export class QuickEvalCodeLensProvider implements CodeLensProvider {
|
||||
async provideCodeLenses(document: TextDocument): Promise<CodeLens[]> {
|
||||
const codeLenses: CodeLens[] = [];
|
||||
|
||||
@@ -43,5 +43,3 @@ class QuickEvalCodeLensProvider implements CodeLensProvider {
|
||||
return codeLenses;
|
||||
}
|
||||
}
|
||||
|
||||
export default QuickEvalCodeLensProvider;
|
||||
@@ -3,18 +3,18 @@ import { dump, load } from "js-yaml";
|
||||
import { basename, join } from "path";
|
||||
import { CancellationToken, window as Window, workspace, Uri } from "vscode";
|
||||
import { LSPErrorCodes, ResponseError } from "vscode-languageclient";
|
||||
import { CodeQLCliServer } from "./cli";
|
||||
import { DatabaseUI } from "./local-databases-ui";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { DatabaseUI } from "../databases/local-databases-ui";
|
||||
import {
|
||||
getInitialQueryContents,
|
||||
getPrimaryDbscheme,
|
||||
getQlPackForDbscheme,
|
||||
showBinaryChoiceDialog,
|
||||
} from "./helpers";
|
||||
import { ProgressCallback, UserCancellationException } from "./progress";
|
||||
import { getErrorMessage } from "./pure/helpers-pure";
|
||||
import { FALLBACK_QLPACK_FILENAME, getQlPackPath } from "./pure/ql";
|
||||
import { App } from "./common/app";
|
||||
} from "../helpers";
|
||||
import { ProgressCallback, UserCancellationException } from "../progress";
|
||||
import { getErrorMessage } from "../pure/helpers-pure";
|
||||
import { FALLBACK_QLPACK_FILENAME, getQlPackPath } from "../pure/ql";
|
||||
import { App } from "../common/app";
|
||||
|
||||
const QUICK_QUERIES_DIR_NAME = "quick-queries";
|
||||
const QUICK_QUERY_QUERY_NAME = "quick-query.ql";
|
||||
@@ -16,6 +16,7 @@ import { ErrorLike } from "./errors";
|
||||
import { DataFlowPaths } from "../variant-analysis/shared/data-flow-paths";
|
||||
import { ExternalApiUsage } from "../data-extensions-editor/external-api-usage";
|
||||
import { ModeledMethod } from "../data-extensions-editor/modeled-method";
|
||||
import { DataExtensionEditorViewState } from "../data-extensions-editor/shared/view-state";
|
||||
|
||||
/**
|
||||
* This module contains types and code that are shared between
|
||||
@@ -481,10 +482,9 @@ export type ToDataFlowPathsMessage = SetDataFlowPathsMessage;
|
||||
|
||||
export type FromDataFlowPathsMessage = CommonFromViewMessages;
|
||||
|
||||
export interface SetDataExtensionEditorInitialDataMessage {
|
||||
t: "setDataExtensionEditorInitialData";
|
||||
extensionPackName: string;
|
||||
modelFilename: string;
|
||||
export interface SetExtensionPackStateMessage {
|
||||
t: "setDataExtensionEditorViewState";
|
||||
viewState: DataExtensionEditorViewState;
|
||||
}
|
||||
|
||||
export interface SetExternalApiUsagesMessage {
|
||||
@@ -536,7 +536,7 @@ export interface GenerateExternalApiMessage {
|
||||
}
|
||||
|
||||
export type ToDataExtensionsEditorMessage =
|
||||
| SetDataExtensionEditorInitialDataMessage
|
||||
| SetExtensionPackStateMessage
|
||||
| SetExternalApiUsagesMessage
|
||||
| ShowProgressMessage
|
||||
| AddModeledMethodsMessage;
|
||||
|
||||
@@ -3,16 +3,23 @@ import {
|
||||
RepositoryWithMetadata,
|
||||
} from "../variant-analysis/shared/repository";
|
||||
import { parseDate } from "./date";
|
||||
import { assertNever } from "./helpers-pure";
|
||||
|
||||
export enum FilterKey {
|
||||
All = "all",
|
||||
WithResults = "withResults",
|
||||
}
|
||||
|
||||
export enum SortKey {
|
||||
Name = "name",
|
||||
Stars = "stars",
|
||||
LastUpdated = "lastUpdated",
|
||||
ResultsCount = "resultsCount",
|
||||
Alphabetically = "alphabetically",
|
||||
Popularity = "popularity",
|
||||
MostRecentCommit = "mostRecentCommit",
|
||||
NumberOfResults = "numberOfResults",
|
||||
}
|
||||
|
||||
export type RepositoriesFilterSortState = {
|
||||
searchValue: string;
|
||||
filterKey: FilterKey;
|
||||
sortKey: SortKey;
|
||||
};
|
||||
|
||||
@@ -22,20 +29,43 @@ export type RepositoriesFilterSortStateWithIds = RepositoriesFilterSortState & {
|
||||
|
||||
export const defaultFilterSortState: RepositoriesFilterSortState = {
|
||||
searchValue: "",
|
||||
sortKey: SortKey.Name,
|
||||
filterKey: FilterKey.All,
|
||||
sortKey: SortKey.Alphabetically,
|
||||
};
|
||||
|
||||
export function matchesFilter(
|
||||
repo: Pick<Repository, "fullName">,
|
||||
item: FilterAndSortableResult,
|
||||
filterSortState: RepositoriesFilterSortState | undefined,
|
||||
): boolean {
|
||||
if (!filterSortState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return repo.fullName
|
||||
.toLowerCase()
|
||||
.includes(filterSortState.searchValue.toLowerCase());
|
||||
return (
|
||||
matchesSearch(item.repository, filterSortState.searchValue) &&
|
||||
matchesFilterKey(item.resultCount, filterSortState.filterKey)
|
||||
);
|
||||
}
|
||||
|
||||
function matchesSearch(
|
||||
repository: SortableRepository,
|
||||
searchValue: string,
|
||||
): boolean {
|
||||
return repository.fullName.toLowerCase().includes(searchValue.toLowerCase());
|
||||
}
|
||||
|
||||
function matchesFilterKey(
|
||||
resultCount: number | undefined,
|
||||
filterKey: FilterKey,
|
||||
): boolean {
|
||||
switch (filterKey) {
|
||||
case FilterKey.All:
|
||||
return true;
|
||||
case FilterKey.WithResults:
|
||||
return resultCount !== undefined && resultCount > 0;
|
||||
default:
|
||||
assertNever(filterKey);
|
||||
}
|
||||
}
|
||||
|
||||
type SortableRepository = Pick<Repository, "fullName"> &
|
||||
@@ -46,7 +76,7 @@ export function compareRepository(
|
||||
): (left: SortableRepository, right: SortableRepository) => number {
|
||||
return (left: SortableRepository, right: SortableRepository) => {
|
||||
// Highest to lowest
|
||||
if (filterSortState?.sortKey === SortKey.Stars) {
|
||||
if (filterSortState?.sortKey === SortKey.Popularity) {
|
||||
const stargazersCount =
|
||||
(right.stargazersCount ?? 0) - (left.stargazersCount ?? 0);
|
||||
if (stargazersCount !== 0) {
|
||||
@@ -55,7 +85,7 @@ export function compareRepository(
|
||||
}
|
||||
|
||||
// Newest to oldest
|
||||
if (filterSortState?.sortKey === SortKey.LastUpdated) {
|
||||
if (filterSortState?.sortKey === SortKey.MostRecentCommit) {
|
||||
const lastUpdated =
|
||||
(parseDate(right.updatedAt)?.getTime() ?? 0) -
|
||||
(parseDate(left.updatedAt)?.getTime() ?? 0);
|
||||
@@ -71,19 +101,24 @@ export function compareRepository(
|
||||
};
|
||||
}
|
||||
|
||||
type SortableResult = {
|
||||
type FilterAndSortableResult = {
|
||||
repository: SortableRepository;
|
||||
resultCount?: number;
|
||||
};
|
||||
|
||||
type FilterAndSortableResultWithIds = {
|
||||
repository: SortableRepository & Pick<Repository, "id">;
|
||||
resultCount?: number;
|
||||
};
|
||||
|
||||
export function compareWithResults(
|
||||
filterSortState: RepositoriesFilterSortState | undefined,
|
||||
): (left: SortableResult, right: SortableResult) => number {
|
||||
): (left: FilterAndSortableResult, right: FilterAndSortableResult) => number {
|
||||
const fallbackSort = compareRepository(filterSortState);
|
||||
|
||||
return (left: SortableResult, right: SortableResult) => {
|
||||
return (left: FilterAndSortableResult, right: FilterAndSortableResult) => {
|
||||
// Highest to lowest
|
||||
if (filterSortState?.sortKey === SortKey.ResultsCount) {
|
||||
if (filterSortState?.sortKey === SortKey.NumberOfResults) {
|
||||
const resultCount = (right.resultCount ?? 0) - (left.resultCount ?? 0);
|
||||
if (resultCount !== 0) {
|
||||
return resultCount;
|
||||
@@ -95,7 +130,7 @@ export function compareWithResults(
|
||||
}
|
||||
|
||||
export function filterAndSortRepositoriesWithResultsByName<
|
||||
T extends SortableResult,
|
||||
T extends FilterAndSortableResult,
|
||||
>(
|
||||
repositories: T[] | undefined,
|
||||
filterSortState: RepositoriesFilterSortState | undefined,
|
||||
@@ -105,11 +140,13 @@ export function filterAndSortRepositoriesWithResultsByName<
|
||||
}
|
||||
|
||||
return repositories
|
||||
.filter((repo) => matchesFilter(repo.repository, filterSortState))
|
||||
.filter((repo) => matchesFilter(repo, filterSortState))
|
||||
.sort(compareWithResults(filterSortState));
|
||||
}
|
||||
|
||||
export function filterAndSortRepositoriesWithResults<T extends SortableResult>(
|
||||
export function filterAndSortRepositoriesWithResults<
|
||||
T extends FilterAndSortableResultWithIds,
|
||||
>(
|
||||
repositories: T[] | undefined,
|
||||
filterSortState: RepositoriesFilterSortStateWithIds | undefined,
|
||||
): T[] | undefined {
|
||||
@@ -117,6 +154,7 @@ export function filterAndSortRepositoriesWithResults<T extends SortableResult>(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If repository IDs are given, then ignore the search value and filter key
|
||||
if (
|
||||
filterSortState?.repositoryIds &&
|
||||
filterSortState.repositoryIds.length > 0
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ChildEvalLogTreeItem, EvalLogTreeItem } from "./eval-log-viewer";
|
||||
import { EvalLogData as EvalLogData } from "./pure/log-summary-parser";
|
||||
import { EvalLogData as EvalLogData } from "../pure/log-summary-parser";
|
||||
|
||||
/** Builds the tree data for the evaluator log viewer for a single query run. */
|
||||
export default class EvalLogTreeBuilder {
|
||||
export class EvalLogTreeBuilder {
|
||||
private queryName: string;
|
||||
private evalLogDataItems: EvalLogData[];
|
||||
|
||||
@@ -8,11 +8,11 @@ import {
|
||||
EventEmitter,
|
||||
TreeItemCollapsibleState,
|
||||
} from "vscode";
|
||||
import { DisposableObject } from "./pure/disposable-object";
|
||||
import { showAndLogExceptionWithTelemetry } from "./helpers";
|
||||
import { asError, getErrorMessage } from "./pure/helpers-pure";
|
||||
import { redactableError } from "./pure/errors";
|
||||
import { EvalLogViewerCommands } from "./common/commands";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { showAndLogExceptionWithTelemetry } from "../helpers";
|
||||
import { asError, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { EvalLogViewerCommands } from "../common/commands";
|
||||
|
||||
export interface EvalLogTreeItem {
|
||||
label?: string;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./eval-log-tree-builder";
|
||||
export * from "./eval-log-viewer";
|
||||
@@ -80,7 +80,7 @@ export class HistoryItemLabelProvider {
|
||||
return {
|
||||
t: item.startTime,
|
||||
q: item.getQueryName(),
|
||||
d: item.initialInfo.databaseInfo.name,
|
||||
d: item.databaseName,
|
||||
r: `(${resultCount} results)`,
|
||||
s: statusString,
|
||||
f: item.getQueryFileName(),
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
import { QueryHistoryConfig } from "../config";
|
||||
import {
|
||||
showAndLogErrorMessage,
|
||||
showAndLogExceptionWithTelemetry,
|
||||
showAndLogInformationMessage,
|
||||
showAndLogWarningMessage,
|
||||
showBinaryChoiceDialog,
|
||||
@@ -25,7 +24,7 @@ import { extLogger } from "../common";
|
||||
import { URLSearchParams } from "url";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { ONE_HOUR_IN_MS, TWO_HOURS_IN_MS } from "../pure/time";
|
||||
import { asError, assertNever, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { assertNever, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { CompletedLocalQueryInfo, LocalQueryInfo } from "../query-results";
|
||||
import {
|
||||
getActionsWorkflowRunUrl,
|
||||
@@ -33,7 +32,7 @@ import {
|
||||
getQueryText,
|
||||
QueryHistoryInfo,
|
||||
} from "./query-history-info";
|
||||
import { DatabaseManager } from "../local-databases";
|
||||
import { DatabaseManager } from "../databases/local-databases";
|
||||
import { registerQueryHistoryScrubber } from "./query-history-scrubber";
|
||||
import {
|
||||
QueryStatus,
|
||||
@@ -45,20 +44,22 @@ import { CliVersionConstraint } from "../cli";
|
||||
import { HistoryItemLabelProvider } from "./history-item-label-provider";
|
||||
import { ResultsView } from "../interface";
|
||||
import { WebviewReveal } from "../interface-utils";
|
||||
import { EvalLogViewer } from "../eval-log-viewer";
|
||||
import EvalLogTreeBuilder from "../eval-log-tree-builder";
|
||||
import { EvalLogTreeBuilder, EvalLogViewer } from "../query-evaluation-logging";
|
||||
import { EvalLogData, parseViewerData } from "../pure/log-summary-parser";
|
||||
import { QueryWithResults } from "../run-queries-shared";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { VariantAnalysisManager } from "../variant-analysis/variant-analysis-manager";
|
||||
import { VariantAnalysisHistoryItem } from "./variant-analysis-history-item";
|
||||
import { getTotalResultCount } from "../variant-analysis/shared/variant-analysis";
|
||||
import { HistoryTreeDataProvider } from "./history-tree-data-provider";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { QueryHistoryDirs } from "./query-history-dirs";
|
||||
import { QueryHistoryCommands } from "../common/commands";
|
||||
import { App } from "../common/app";
|
||||
import { tryOpenExternalFile } from "../vscode-utils/external-files";
|
||||
import {
|
||||
createMultiSelectionCommand,
|
||||
createSingleSelectionCommand,
|
||||
} from "../common/selection-commands";
|
||||
|
||||
/**
|
||||
* query-history-manager.ts
|
||||
@@ -235,36 +236,78 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
"codeQLQueryHistory.sortByDate": this.handleSortByDate.bind(this),
|
||||
"codeQLQueryHistory.sortByCount": this.handleSortByCount.bind(this),
|
||||
|
||||
"codeQLQueryHistory.openQueryTitleMenu": this.handleOpenQuery.bind(this),
|
||||
"codeQLQueryHistory.openQueryContextMenu":
|
||||
"codeQLQueryHistory.openQueryContextMenu": createSingleSelectionCommand(
|
||||
this.handleOpenQuery.bind(this),
|
||||
"codeQLQueryHistory.removeHistoryItemTitleMenu":
|
||||
this.handleRemoveHistoryItem.bind(this),
|
||||
"query",
|
||||
),
|
||||
"codeQLQueryHistory.removeHistoryItemContextMenu":
|
||||
this.handleRemoveHistoryItem.bind(this),
|
||||
createMultiSelectionCommand(this.handleRemoveHistoryItem.bind(this)),
|
||||
"codeQLQueryHistory.removeHistoryItemContextInline":
|
||||
this.handleRemoveHistoryItem.bind(this),
|
||||
"codeQLQueryHistory.renameItem": this.handleRenameItem.bind(this),
|
||||
createMultiSelectionCommand(this.handleRemoveHistoryItem.bind(this)),
|
||||
"codeQLQueryHistory.renameItem": createSingleSelectionCommand(
|
||||
this.handleRenameItem.bind(this),
|
||||
"query",
|
||||
),
|
||||
"codeQLQueryHistory.compareWith": this.handleCompareWith.bind(this),
|
||||
"codeQLQueryHistory.showEvalLog": this.handleShowEvalLog.bind(this),
|
||||
"codeQLQueryHistory.showEvalLogSummary":
|
||||
"codeQLQueryHistory.showEvalLog": createSingleSelectionCommand(
|
||||
this.handleShowEvalLog.bind(this),
|
||||
"query",
|
||||
),
|
||||
"codeQLQueryHistory.showEvalLogSummary": createSingleSelectionCommand(
|
||||
this.handleShowEvalLogSummary.bind(this),
|
||||
"codeQLQueryHistory.showEvalLogViewer":
|
||||
"query",
|
||||
),
|
||||
"codeQLQueryHistory.showEvalLogViewer": createSingleSelectionCommand(
|
||||
this.handleShowEvalLogViewer.bind(this),
|
||||
"codeQLQueryHistory.showQueryLog": this.handleShowQueryLog.bind(this),
|
||||
"codeQLQueryHistory.showQueryText": this.handleShowQueryText.bind(this),
|
||||
"codeQLQueryHistory.openQueryDirectory":
|
||||
"query",
|
||||
),
|
||||
"codeQLQueryHistory.showQueryLog": createSingleSelectionCommand(
|
||||
this.handleShowQueryLog.bind(this),
|
||||
"query",
|
||||
),
|
||||
"codeQLQueryHistory.showQueryText": createSingleSelectionCommand(
|
||||
this.handleShowQueryText.bind(this),
|
||||
"query",
|
||||
),
|
||||
"codeQLQueryHistory.openQueryDirectory": createSingleSelectionCommand(
|
||||
this.handleOpenQueryDirectory.bind(this),
|
||||
"codeQLQueryHistory.cancel": this.handleCancel.bind(this),
|
||||
"codeQLQueryHistory.exportResults": this.handleExportResults.bind(this),
|
||||
"codeQLQueryHistory.viewCsvResults": this.handleViewCsvResults.bind(this),
|
||||
"codeQLQueryHistory.viewCsvAlerts": this.handleViewCsvAlerts.bind(this),
|
||||
"codeQLQueryHistory.viewSarifAlerts":
|
||||
"query",
|
||||
),
|
||||
"codeQLQueryHistory.cancel": createMultiSelectionCommand(
|
||||
this.handleCancel.bind(this),
|
||||
),
|
||||
"codeQLQueryHistory.exportResults": createSingleSelectionCommand(
|
||||
this.handleExportResults.bind(this),
|
||||
"query",
|
||||
),
|
||||
"codeQLQueryHistory.viewCsvResults": createSingleSelectionCommand(
|
||||
this.handleViewCsvResults.bind(this),
|
||||
"query",
|
||||
),
|
||||
"codeQLQueryHistory.viewCsvAlerts": createSingleSelectionCommand(
|
||||
this.handleViewCsvAlerts.bind(this),
|
||||
"query",
|
||||
),
|
||||
"codeQLQueryHistory.viewSarifAlerts": createSingleSelectionCommand(
|
||||
this.handleViewSarifAlerts.bind(this),
|
||||
"codeQLQueryHistory.viewDil": this.handleViewDil.bind(this),
|
||||
"codeQLQueryHistory.itemClicked": this.handleItemClicked.bind(this),
|
||||
"codeQLQueryHistory.openOnGithub": this.handleOpenOnGithub.bind(this),
|
||||
"codeQLQueryHistory.copyRepoList": this.handleCopyRepoList.bind(this),
|
||||
"query",
|
||||
),
|
||||
"codeQLQueryHistory.viewDil": createSingleSelectionCommand(
|
||||
this.handleViewDil.bind(this),
|
||||
"query",
|
||||
),
|
||||
"codeQLQueryHistory.itemClicked": createSingleSelectionCommand(
|
||||
this.handleItemClicked.bind(this),
|
||||
"query",
|
||||
),
|
||||
"codeQLQueryHistory.openOnGithub": createSingleSelectionCommand(
|
||||
this.handleOpenOnGithub.bind(this),
|
||||
"query",
|
||||
),
|
||||
"codeQLQueryHistory.copyRepoList": createSingleSelectionCommand(
|
||||
this.handleCopyRepoList.bind(this),
|
||||
"query",
|
||||
),
|
||||
|
||||
"codeQL.exportSelectedVariantAnalysisResults":
|
||||
this.exportSelectedVariantAnalysisResults.bind(this),
|
||||
@@ -398,39 +441,26 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
);
|
||||
}
|
||||
|
||||
async handleOpenQuery(
|
||||
singleItem: QueryHistoryInfo | undefined,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
): Promise<void> {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (finalSingleItem.t === "variant-analysis") {
|
||||
await this.variantAnalysisManager.openQueryFile(
|
||||
finalSingleItem.variantAnalysis.id,
|
||||
);
|
||||
async handleOpenQuery(item: QueryHistoryInfo): Promise<void> {
|
||||
if (item.t === "variant-analysis") {
|
||||
await this.variantAnalysisManager.openQueryFile(item.variantAnalysis.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let queryPath: string;
|
||||
switch (finalSingleItem.t) {
|
||||
switch (item.t) {
|
||||
case "local":
|
||||
queryPath = finalSingleItem.initialInfo.queryPath;
|
||||
queryPath = item.initialInfo.queryPath;
|
||||
break;
|
||||
default:
|
||||
assertNever(finalSingleItem);
|
||||
assertNever(item);
|
||||
}
|
||||
const textDocument = await workspace.openTextDocument(Uri.file(queryPath));
|
||||
const editor = await window.showTextDocument(textDocument, ViewColumn.One);
|
||||
|
||||
if (finalSingleItem.t === "local") {
|
||||
const queryText = finalSingleItem.initialInfo.queryText;
|
||||
if (queryText !== undefined && finalSingleItem.initialInfo.isQuickQuery) {
|
||||
if (item.t === "local") {
|
||||
const queryText = item.initialInfo.queryText;
|
||||
if (queryText !== undefined && item.initialInfo.isQuickQuery) {
|
||||
await editor.edit((edit) =>
|
||||
edit.replace(
|
||||
textDocument.validateRange(
|
||||
@@ -461,17 +491,9 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
);
|
||||
}
|
||||
|
||||
async handleRemoveHistoryItem(
|
||||
singleItem: QueryHistoryInfo | undefined,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
const toDelete = finalMultiSelect || [finalSingleItem];
|
||||
async handleRemoveHistoryItem(items: QueryHistoryInfo[]) {
|
||||
await Promise.all(
|
||||
toDelete.map(async (item) => {
|
||||
items.map(async (item) => {
|
||||
if (item.t === "local") {
|
||||
// Removing in progress local queries is not supported. They must be cancelled first.
|
||||
if (item.status !== QueryStatus.InProgress) {
|
||||
@@ -561,22 +583,10 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
}
|
||||
|
||||
async handleRenameItem(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
): Promise<void> {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
async handleRenameItem(item: QueryHistoryInfo): Promise<void> {
|
||||
const response = await window.showInputBox({
|
||||
placeHolder: `(use default: ${this.queryHistoryConfigListener.format})`,
|
||||
value: finalSingleItem.userSpecifiedLabel ?? "",
|
||||
value: item.userSpecifiedLabel ?? "",
|
||||
title: "Set query label",
|
||||
prompt:
|
||||
"Set the query history item label. See the description of the codeQL.queryHistory.format setting for more information.",
|
||||
@@ -584,102 +594,81 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
// undefined response means the user cancelled the dialog; don't change anything
|
||||
if (response !== undefined) {
|
||||
// Interpret empty string response as 'go back to using default'
|
||||
finalSingleItem.userSpecifiedLabel =
|
||||
response === "" ? undefined : response;
|
||||
item.userSpecifiedLabel = response === "" ? undefined : response;
|
||||
await this.refreshTreeView();
|
||||
}
|
||||
}
|
||||
|
||||
isSuccessfulCompletedLocalQueryInfo(
|
||||
item: QueryHistoryInfo,
|
||||
): item is CompletedLocalQueryInfo {
|
||||
return item.t === "local" && item.completedQuery?.successful === true;
|
||||
}
|
||||
|
||||
async handleCompareWith(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
multiSelect ||= [singleItem];
|
||||
|
||||
try {
|
||||
// local queries only
|
||||
if (finalSingleItem?.t !== "local") {
|
||||
throw new Error("Please select a local query.");
|
||||
}
|
||||
|
||||
if (!finalSingleItem.completedQuery?.successful) {
|
||||
throw new Error(
|
||||
"Please select a query that has completed successfully.",
|
||||
);
|
||||
}
|
||||
|
||||
const from = this.compareWithItem || singleItem;
|
||||
const to = await this.findOtherQueryToCompare(from, finalMultiSelect);
|
||||
|
||||
if (from.completed && to?.completed) {
|
||||
await this.doCompareCallback(
|
||||
from as CompletedLocalQueryInfo,
|
||||
to as CompletedLocalQueryInfo,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
redactableError(
|
||||
asError(e),
|
||||
)`Failed to compare queries: ${getErrorMessage(e)}`,
|
||||
if (
|
||||
!this.isSuccessfulCompletedLocalQueryInfo(singleItem) ||
|
||||
!multiSelect.every(this.isSuccessfulCompletedLocalQueryInfo)
|
||||
) {
|
||||
throw new Error(
|
||||
"Please only select local queries that have completed successfully.",
|
||||
);
|
||||
}
|
||||
|
||||
const fromItem = this.getFromQueryToCompare(singleItem, multiSelect);
|
||||
|
||||
let toItem: CompletedLocalQueryInfo | undefined = undefined;
|
||||
try {
|
||||
toItem = await this.findOtherQueryToCompare(fromItem, multiSelect);
|
||||
} catch (e) {
|
||||
void showAndLogErrorMessage(
|
||||
`Failed to compare queries: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (toItem !== undefined) {
|
||||
await this.doCompareCallback(fromItem, toItem);
|
||||
}
|
||||
}
|
||||
|
||||
async handleItemClicked(
|
||||
singleItem: QueryHistoryInfo | undefined,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.treeDataProvider.setCurrentItem(finalSingleItem);
|
||||
async handleItemClicked(item: QueryHistoryInfo) {
|
||||
this.treeDataProvider.setCurrentItem(item);
|
||||
|
||||
const now = new Date();
|
||||
const prevItemClick = this.lastItemClick;
|
||||
this.lastItemClick = { time: now, item: finalSingleItem };
|
||||
this.lastItemClick = { time: now, item };
|
||||
|
||||
if (
|
||||
prevItemClick !== undefined &&
|
||||
now.valueOf() - prevItemClick.time.valueOf() < DOUBLE_CLICK_TIME &&
|
||||
finalSingleItem === prevItemClick.item
|
||||
item === prevItemClick.item
|
||||
) {
|
||||
// show original query file on double click
|
||||
await this.handleOpenQuery(finalSingleItem, [finalSingleItem]);
|
||||
await this.handleOpenQuery(item);
|
||||
} else if (
|
||||
finalSingleItem.t === "variant-analysis" ||
|
||||
finalSingleItem.status === QueryStatus.Completed
|
||||
item.t === "variant-analysis" ||
|
||||
item.status === QueryStatus.Completed
|
||||
) {
|
||||
// show results on single click (if results view is available)
|
||||
await this.openQueryResults(finalSingleItem);
|
||||
await this.openQueryResults(item);
|
||||
}
|
||||
}
|
||||
|
||||
async handleShowQueryLog(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
) {
|
||||
async handleShowQueryLog(item: QueryHistoryInfo) {
|
||||
// Local queries only
|
||||
if (!this.assertSingleQuery(multiSelect) || singleItem?.t !== "local") {
|
||||
if (item?.t !== "local" || !item.completedQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!singleItem.completedQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (singleItem.completedQuery.logFileLocation) {
|
||||
if (item.completedQuery.logFileLocation) {
|
||||
await tryOpenExternalFile(
|
||||
this.app.commands,
|
||||
singleItem.completedQuery.logFileLocation,
|
||||
item.completedQuery.logFileLocation,
|
||||
);
|
||||
} else {
|
||||
void showAndLogWarningMessage("No log file available");
|
||||
@@ -704,36 +693,24 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
throw new Error("Unable to get query directory");
|
||||
}
|
||||
|
||||
async handleOpenQueryDirectory(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
async handleOpenQueryDirectory(item: QueryHistoryInfo) {
|
||||
let externalFilePath: string | undefined;
|
||||
if (finalSingleItem.t === "local") {
|
||||
if (finalSingleItem.completedQuery) {
|
||||
if (item.t === "local") {
|
||||
if (item.completedQuery) {
|
||||
externalFilePath = join(
|
||||
finalSingleItem.completedQuery.query.querySaveDir,
|
||||
item.completedQuery.query.querySaveDir,
|
||||
"timestamp",
|
||||
);
|
||||
}
|
||||
} else if (finalSingleItem.t === "variant-analysis") {
|
||||
} else if (item.t === "variant-analysis") {
|
||||
externalFilePath = join(
|
||||
this.variantAnalysisManager.getVariantAnalysisStorageLocation(
|
||||
finalSingleItem.variantAnalysis.id,
|
||||
item.variantAnalysis.id,
|
||||
),
|
||||
"timestamp",
|
||||
);
|
||||
} else {
|
||||
assertNever(finalSingleItem);
|
||||
assertNever(item);
|
||||
}
|
||||
|
||||
if (externalFilePath) {
|
||||
@@ -778,65 +755,30 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
);
|
||||
}
|
||||
|
||||
async handleShowEvalLog(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
|
||||
// Only applicable to an individual local query
|
||||
if (
|
||||
!this.assertSingleQuery(finalMultiSelect) ||
|
||||
!finalSingleItem ||
|
||||
finalSingleItem.t !== "local"
|
||||
) {
|
||||
async handleShowEvalLog(item: QueryHistoryInfo) {
|
||||
if (item.t !== "local") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (finalSingleItem.evalLogLocation) {
|
||||
await tryOpenExternalFile(
|
||||
this.app.commands,
|
||||
finalSingleItem.evalLogLocation,
|
||||
);
|
||||
if (item.evalLogLocation) {
|
||||
await tryOpenExternalFile(this.app.commands, item.evalLogLocation);
|
||||
} else {
|
||||
this.warnNoEvalLogs();
|
||||
}
|
||||
}
|
||||
|
||||
async handleShowEvalLogSummary(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
|
||||
// Only applicable to an individual local query
|
||||
if (
|
||||
!this.assertSingleQuery(finalMultiSelect) ||
|
||||
!finalSingleItem ||
|
||||
finalSingleItem.t !== "local"
|
||||
) {
|
||||
async handleShowEvalLogSummary(item: QueryHistoryInfo) {
|
||||
if (item.t !== "local") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (finalSingleItem.evalLogSummaryLocation) {
|
||||
await tryOpenExternalFile(
|
||||
this.app.commands,
|
||||
finalSingleItem.evalLogSummaryLocation,
|
||||
);
|
||||
if (item.evalLogSummaryLocation) {
|
||||
await tryOpenExternalFile(this.app.commands, item.evalLogSummaryLocation);
|
||||
return;
|
||||
}
|
||||
|
||||
// Summary log file doesn't exist.
|
||||
if (
|
||||
finalSingleItem.evalLogLocation &&
|
||||
(await pathExists(finalSingleItem.evalLogLocation))
|
||||
) {
|
||||
if (item.evalLogLocation && (await pathExists(item.evalLogLocation))) {
|
||||
// If raw log does exist, then the summary log is still being generated.
|
||||
this.warnInProgressEvalLogSummary();
|
||||
} else {
|
||||
@@ -844,25 +786,13 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
}
|
||||
|
||||
async handleShowEvalLogViewer(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
// Only applicable to an individual local query
|
||||
if (
|
||||
!this.assertSingleQuery(finalMultiSelect) ||
|
||||
!finalSingleItem ||
|
||||
finalSingleItem.t !== "local"
|
||||
) {
|
||||
async handleShowEvalLogViewer(item: QueryHistoryInfo) {
|
||||
if (item.t !== "local") {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the JSON summary file location wasn't saved, display error
|
||||
if (finalSingleItem.jsonEvalLogSummaryLocation === undefined) {
|
||||
if (item.jsonEvalLogSummaryLocation === undefined) {
|
||||
this.warnInProgressEvalLogViewer();
|
||||
return;
|
||||
}
|
||||
@@ -870,32 +800,22 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
// TODO(angelapwen): Stream the file in.
|
||||
try {
|
||||
const evalLogData: EvalLogData[] = await parseViewerData(
|
||||
finalSingleItem.jsonEvalLogSummaryLocation,
|
||||
item.jsonEvalLogSummaryLocation,
|
||||
);
|
||||
const evalLogTreeBuilder = new EvalLogTreeBuilder(
|
||||
finalSingleItem.getQueryName(),
|
||||
item.getQueryName(),
|
||||
evalLogData,
|
||||
);
|
||||
this.evalLogViewer.updateRoots(await evalLogTreeBuilder.getRoots());
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Could not read evaluator log summary JSON file to generate viewer data at ${finalSingleItem.jsonEvalLogSummaryLocation}.`,
|
||||
`Could not read evaluator log summary JSON file to generate viewer data at ${item.jsonEvalLogSummaryLocation}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async handleCancel(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
|
||||
const selected = finalMultiSelect || [finalSingleItem];
|
||||
|
||||
const results = selected.map(async (item) => {
|
||||
async handleCancel(items: QueryHistoryInfo[]) {
|
||||
const results = items.map(async (item) => {
|
||||
if (item.status === QueryStatus.InProgress) {
|
||||
if (item.t === "local") {
|
||||
item.cancel();
|
||||
@@ -912,63 +832,32 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
await Promise.all(results);
|
||||
}
|
||||
|
||||
async handleShowQueryText(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[] = [],
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (finalSingleItem.t === "variant-analysis") {
|
||||
await this.variantAnalysisManager.openQueryText(
|
||||
finalSingleItem.variantAnalysis.id,
|
||||
);
|
||||
async handleShowQueryText(item: QueryHistoryInfo) {
|
||||
if (item.t === "variant-analysis") {
|
||||
await this.variantAnalysisManager.openQueryText(item.variantAnalysis.id);
|
||||
return;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
isQuickEval: String(
|
||||
!!(
|
||||
finalSingleItem.t === "local" &&
|
||||
finalSingleItem.initialInfo.quickEvalPosition
|
||||
),
|
||||
!!(item.t === "local" && item.initialInfo.quickEvalPosition),
|
||||
),
|
||||
queryText: encodeURIComponent(getQueryText(finalSingleItem)),
|
||||
queryText: encodeURIComponent(getQueryText(item)),
|
||||
});
|
||||
|
||||
const queryId = getQueryId(finalSingleItem);
|
||||
const queryId = getQueryId(item);
|
||||
|
||||
const uri = Uri.parse(`codeql:${queryId}.ql?${params.toString()}`, true);
|
||||
const doc = await workspace.openTextDocument(uri);
|
||||
await window.showTextDocument(doc, { preview: false });
|
||||
}
|
||||
|
||||
async handleViewSarifAlerts(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
|
||||
// Local queries only
|
||||
if (
|
||||
!this.assertSingleQuery(finalMultiSelect) ||
|
||||
!finalSingleItem ||
|
||||
finalSingleItem.t !== "local" ||
|
||||
!finalSingleItem.completedQuery
|
||||
) {
|
||||
async handleViewSarifAlerts(item: QueryHistoryInfo) {
|
||||
if (item.t !== "local" || !item.completedQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
const query = finalSingleItem.completedQuery.query;
|
||||
const query = item.completedQuery.query;
|
||||
const hasInterpretedResults = query.canHaveInterpretedResults();
|
||||
if (hasInterpretedResults) {
|
||||
await tryOpenExternalFile(
|
||||
@@ -976,32 +865,18 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
query.resultsPaths.interpretedResultsPath,
|
||||
);
|
||||
} else {
|
||||
const label = this.labelProvider.getLabel(finalSingleItem);
|
||||
const label = this.labelProvider.getLabel(item);
|
||||
void showAndLogInformationMessage(
|
||||
`Query ${label} has no interpreted results.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async handleViewCsvResults(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
|
||||
// Local queries only
|
||||
if (
|
||||
!this.assertSingleQuery(finalMultiSelect) ||
|
||||
!finalSingleItem ||
|
||||
finalSingleItem.t !== "local" ||
|
||||
!finalSingleItem.completedQuery
|
||||
) {
|
||||
async handleViewCsvResults(item: QueryHistoryInfo) {
|
||||
if (item.t !== "local" || !item.completedQuery) {
|
||||
return;
|
||||
}
|
||||
const query = finalSingleItem.completedQuery.query;
|
||||
const query = item.completedQuery.query;
|
||||
if (await query.hasCsv()) {
|
||||
void tryOpenExternalFile(this.app.commands, query.csvPath);
|
||||
return;
|
||||
@@ -1011,79 +886,37 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
}
|
||||
|
||||
async handleViewCsvAlerts(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
|
||||
// Local queries only
|
||||
if (
|
||||
!this.assertSingleQuery(finalMultiSelect) ||
|
||||
!finalSingleItem ||
|
||||
finalSingleItem.t !== "local" ||
|
||||
!finalSingleItem.completedQuery
|
||||
) {
|
||||
async handleViewCsvAlerts(item: QueryHistoryInfo) {
|
||||
if (item.t !== "local" || !item.completedQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
await tryOpenExternalFile(
|
||||
this.app.commands,
|
||||
await finalSingleItem.completedQuery.query.ensureCsvAlerts(
|
||||
await item.completedQuery.query.ensureCsvAlerts(
|
||||
this.qs.cliServer,
|
||||
this.dbm,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async handleViewDil(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
|
||||
// Local queries only
|
||||
if (
|
||||
!this.assertSingleQuery(finalMultiSelect) ||
|
||||
!finalSingleItem ||
|
||||
finalSingleItem.t !== "local" ||
|
||||
!finalSingleItem.completedQuery
|
||||
) {
|
||||
async handleViewDil(item: QueryHistoryInfo) {
|
||||
if (item.t !== "local" || !item.completedQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
await tryOpenExternalFile(
|
||||
this.app.commands,
|
||||
await finalSingleItem.completedQuery.query.ensureDilPath(
|
||||
this.qs.cliServer,
|
||||
),
|
||||
await item.completedQuery.query.ensureDilPath(this.qs.cliServer),
|
||||
);
|
||||
}
|
||||
|
||||
async handleOpenOnGithub(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem) {
|
||||
async handleOpenOnGithub(item: QueryHistoryInfo) {
|
||||
if (item.t !== "variant-analysis") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (finalSingleItem.t === "local") {
|
||||
return;
|
||||
}
|
||||
|
||||
const actionsWorkflowRunUrl = getActionsWorkflowRunUrl(finalSingleItem);
|
||||
const actionsWorkflowRunUrl = getActionsWorkflowRunUrl(item);
|
||||
|
||||
await this.app.commands.execute(
|
||||
"vscode.open",
|
||||
@@ -1091,51 +924,23 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
);
|
||||
}
|
||||
|
||||
async handleCopyRepoList(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
|
||||
// Variant analyses only
|
||||
if (
|
||||
!this.assertSingleQuery(finalMultiSelect) ||
|
||||
!finalSingleItem ||
|
||||
finalSingleItem.t !== "variant-analysis"
|
||||
) {
|
||||
async handleCopyRepoList(item: QueryHistoryInfo) {
|
||||
if (item.t !== "variant-analysis") {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.app.commands.execute(
|
||||
"codeQL.copyVariantAnalysisRepoList",
|
||||
finalSingleItem.variantAnalysis.id,
|
||||
item.variantAnalysis.id,
|
||||
);
|
||||
}
|
||||
|
||||
async handleExportResults(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
): Promise<void> {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(
|
||||
singleItem,
|
||||
multiSelect,
|
||||
);
|
||||
|
||||
// Variant analysis only
|
||||
if (
|
||||
!this.assertSingleQuery(finalMultiSelect) ||
|
||||
!finalSingleItem ||
|
||||
finalSingleItem.t !== "variant-analysis"
|
||||
) {
|
||||
async handleExportResults(item: QueryHistoryInfo): Promise<void> {
|
||||
if (item.t !== "variant-analysis") {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.variantAnalysisManager.exportResults(
|
||||
finalSingleItem.variantAnalysis.id,
|
||||
);
|
||||
await this.variantAnalysisManager.exportResults(item.variantAnalysis.id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1179,58 +984,56 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
}
|
||||
|
||||
private async findOtherQueryToCompare(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[],
|
||||
): Promise<CompletedLocalQueryInfo | undefined> {
|
||||
// Variant analyses cannot be compared
|
||||
private getFromQueryToCompare(
|
||||
singleItem: CompletedLocalQueryInfo,
|
||||
multiSelect: CompletedLocalQueryInfo[],
|
||||
): CompletedLocalQueryInfo {
|
||||
if (
|
||||
singleItem.t !== "local" ||
|
||||
multiSelect.some((s) => s.t !== "local") ||
|
||||
!singleItem.completedQuery
|
||||
this.compareWithItem &&
|
||||
this.isSuccessfulCompletedLocalQueryInfo(this.compareWithItem) &&
|
||||
multiSelect.includes(this.compareWithItem)
|
||||
) {
|
||||
return undefined;
|
||||
return this.compareWithItem;
|
||||
} else {
|
||||
return singleItem;
|
||||
}
|
||||
const dbName = singleItem.initialInfo.databaseInfo.name;
|
||||
}
|
||||
|
||||
// if exactly 2 queries are selected, use those
|
||||
if (multiSelect?.length === 2) {
|
||||
// return the query that is not the first selected one
|
||||
const otherQuery = (
|
||||
singleItem === multiSelect[0] ? multiSelect[1] : multiSelect[0]
|
||||
) as LocalQueryInfo;
|
||||
if (!otherQuery.completedQuery) {
|
||||
throw new Error("Please select a completed query.");
|
||||
}
|
||||
if (!otherQuery.completedQuery.successful) {
|
||||
throw new Error("Please select a successful query.");
|
||||
}
|
||||
if (otherQuery.initialInfo.databaseInfo.name !== dbName) {
|
||||
private async findOtherQueryToCompare(
|
||||
fromItem: CompletedLocalQueryInfo,
|
||||
allSelectedItems: CompletedLocalQueryInfo[],
|
||||
): Promise<CompletedLocalQueryInfo | undefined> {
|
||||
const dbName = fromItem.databaseName;
|
||||
|
||||
// If exactly 2 items are selected, return the one that
|
||||
// isn't being used as the "from" item.
|
||||
if (allSelectedItems.length === 2) {
|
||||
const otherItem =
|
||||
fromItem === allSelectedItems[0]
|
||||
? allSelectedItems[1]
|
||||
: allSelectedItems[0];
|
||||
if (otherItem.databaseName !== dbName) {
|
||||
throw new Error("Query databases must be the same.");
|
||||
}
|
||||
return otherQuery as CompletedLocalQueryInfo;
|
||||
return otherItem;
|
||||
}
|
||||
|
||||
if (multiSelect?.length > 2) {
|
||||
if (allSelectedItems.length > 2) {
|
||||
throw new Error("Please select no more than 2 queries.");
|
||||
}
|
||||
|
||||
// otherwise, let the user choose
|
||||
// Otherwise, present a dialog so the user can choose the item they want to use.
|
||||
const comparableQueryLabels = this.treeDataProvider.allHistory
|
||||
.filter(this.isSuccessfulCompletedLocalQueryInfo)
|
||||
.filter(
|
||||
(otherQuery) =>
|
||||
otherQuery !== singleItem &&
|
||||
otherQuery.t === "local" &&
|
||||
otherQuery.completedQuery &&
|
||||
otherQuery.completedQuery.successful &&
|
||||
otherQuery.initialInfo.databaseInfo.name === dbName,
|
||||
(otherItem) =>
|
||||
otherItem !== fromItem && otherItem.databaseName === dbName,
|
||||
)
|
||||
.map((item) => ({
|
||||
label: this.labelProvider.getLabel(item),
|
||||
description: (item as CompletedLocalQueryInfo).initialInfo.databaseInfo
|
||||
.name,
|
||||
detail: (item as CompletedLocalQueryInfo).completedQuery.statusString,
|
||||
query: item as CompletedLocalQueryInfo,
|
||||
description: item.databaseName,
|
||||
detail: item.completedQuery.statusString,
|
||||
query: item,
|
||||
}));
|
||||
if (comparableQueryLabels.length < 1) {
|
||||
throw new Error("No other queries available to compare with.");
|
||||
@@ -1240,17 +1043,6 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
return choice?.query;
|
||||
}
|
||||
|
||||
private assertSingleQuery(
|
||||
multiSelect: QueryHistoryInfo[] = [],
|
||||
message = "Please select a single query.",
|
||||
) {
|
||||
if (multiSelect.length > 1) {
|
||||
void showAndLogErrorMessage(message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the compare with source query. This ensures that all compare command invocations
|
||||
* when exactly 2 queries are selected always have the proper _from_ query. Always use
|
||||
@@ -1280,52 +1072,6 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If no items are selected, attempt to grab the selection from the treeview.
|
||||
* However, often the treeview itself does not have any selection. In this case,
|
||||
* grab the selection from the `treeDataProvider` current item.
|
||||
*
|
||||
* We need to use this method because when clicking on commands from the view title
|
||||
* bar, the selections are not passed in.
|
||||
*
|
||||
* @param singleItem the single item selected, or undefined if no item is selected
|
||||
* @param multiSelect a multi-select or undefined if no items are selected
|
||||
*/
|
||||
private determineSelection(
|
||||
singleItem: QueryHistoryInfo | undefined,
|
||||
multiSelect: QueryHistoryInfo[] | undefined,
|
||||
): {
|
||||
finalSingleItem: QueryHistoryInfo | undefined;
|
||||
finalMultiSelect: QueryHistoryInfo[];
|
||||
} {
|
||||
if (!singleItem && !multiSelect?.[0]) {
|
||||
const selection = this.treeView.selection;
|
||||
const current = this.treeDataProvider.getCurrent();
|
||||
if (selection?.length) {
|
||||
return {
|
||||
finalSingleItem: selection[0],
|
||||
finalMultiSelect: [...selection],
|
||||
};
|
||||
} else if (current) {
|
||||
return {
|
||||
finalSingleItem: current,
|
||||
finalMultiSelect: [current],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ensure we only return undefined if we have neither a single or multi-selecion
|
||||
if (singleItem && !multiSelect?.[0]) {
|
||||
multiSelect = [singleItem];
|
||||
} else if (!singleItem && multiSelect?.[0]) {
|
||||
singleItem = multiSelect[0];
|
||||
}
|
||||
return {
|
||||
finalSingleItem: singleItem,
|
||||
finalMultiSelect: multiSelect || [],
|
||||
};
|
||||
}
|
||||
|
||||
async refreshTreeView(): Promise<void> {
|
||||
this.treeDataProvider.refresh();
|
||||
await this.writeQueryHistory();
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
QueryEvaluationInfo,
|
||||
QueryWithResults,
|
||||
} from "./run-queries-shared";
|
||||
import { formatLegacyMessage } from "./legacy-query-server/run-queries";
|
||||
import { formatLegacyMessage } from "./query-server/legacy";
|
||||
import { sarifParser } from "./sarif-parser";
|
||||
|
||||
/**
|
||||
@@ -313,4 +313,8 @@ export class LocalQueryInfo {
|
||||
return QueryStatus.Failed;
|
||||
}
|
||||
}
|
||||
|
||||
get databaseName() {
|
||||
return this.initialInfo.databaseInfo.name;
|
||||
}
|
||||
}
|
||||
|
||||
5
extensions/ql-vscode/src/query-server/index.ts
Normal file
5
extensions/ql-vscode/src/query-server/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from "./new-query-runner";
|
||||
export * from "./query-runner";
|
||||
export * from "./query-server-client";
|
||||
export * from "./run-queries";
|
||||
export * from "./server-process";
|
||||
4
extensions/ql-vscode/src/query-server/legacy/index.ts
Normal file
4
extensions/ql-vscode/src/query-server/legacy/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./legacy-query-runner";
|
||||
export * from "./query-server-client";
|
||||
export * from "./run-queries";
|
||||
export * from "./upgrades";
|
||||
@@ -1,16 +1,20 @@
|
||||
import { CancellationToken } from "vscode";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import { Logger } from "../common";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { CodeQLCliServer } from "../../cli";
|
||||
import { ProgressCallback } from "../../progress";
|
||||
import { Logger } from "../../common";
|
||||
import { DatabaseItem } from "../../databases/local-databases";
|
||||
import {
|
||||
Dataset,
|
||||
deregisterDatabases,
|
||||
registerDatabases,
|
||||
} from "../pure/legacy-messages";
|
||||
import { CoreQueryResults, CoreQueryTarget, QueryRunner } from "../queryRunner";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
import { QueryServerClient } from "./queryserver-client";
|
||||
} from "../../pure/legacy-messages";
|
||||
import {
|
||||
CoreQueryResults,
|
||||
CoreQueryTarget,
|
||||
QueryRunner,
|
||||
} from "../query-runner";
|
||||
import { QueryOutputDir } from "../../run-queries-shared";
|
||||
import { QueryServerClient } from "./query-server-client";
|
||||
import {
|
||||
clearCacheInDatabase,
|
||||
compileAndRunQueryAgainstDatabaseCore,
|
||||
@@ -1,21 +1,21 @@
|
||||
import { ensureFile } from "fs-extra";
|
||||
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { DisposableObject } from "../../pure/disposable-object";
|
||||
import { CancellationToken } from "vscode";
|
||||
import { createMessageConnection, RequestType } from "vscode-jsonrpc/node";
|
||||
import * as cli from "../cli";
|
||||
import { QueryServerConfig } from "../config";
|
||||
import { Logger, ProgressReporter } from "../common";
|
||||
import * as cli from "../../cli";
|
||||
import { QueryServerConfig } from "../../config";
|
||||
import { Logger, ProgressReporter } from "../../common";
|
||||
import {
|
||||
completeQuery,
|
||||
EvaluationResult,
|
||||
progress,
|
||||
ProgressMessage,
|
||||
WithProgressId,
|
||||
} from "../pure/legacy-messages";
|
||||
import { ProgressCallback, ProgressTask } from "../progress";
|
||||
import { ServerProcess } from "../json-rpc-server";
|
||||
import { App } from "../common/app";
|
||||
} from "../../pure/legacy-messages";
|
||||
import { ProgressCallback, ProgressTask } from "../../progress";
|
||||
import { ServerProcess } from "../server-process";
|
||||
import { App } from "../../common/app";
|
||||
|
||||
type WithProgressReporting = (
|
||||
task: (
|
||||
@@ -3,29 +3,29 @@ import { basename } from "path";
|
||||
import { CancellationToken, Uri } from "vscode";
|
||||
import { LSPErrorCodes, ResponseError } from "vscode-languageclient";
|
||||
|
||||
import * as cli from "../cli";
|
||||
import * as cli from "../../cli";
|
||||
import {
|
||||
DatabaseContentsWithDbScheme,
|
||||
DatabaseItem,
|
||||
DatabaseResolver,
|
||||
} from "../local-databases";
|
||||
} from "../../databases/local-databases";
|
||||
import {
|
||||
showAndLogExceptionWithTelemetry,
|
||||
showAndLogWarningMessage,
|
||||
upgradesTmpDir,
|
||||
} from "../helpers";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import { QueryMetadata } from "../pure/interface-types";
|
||||
import { extLogger, Logger } from "../common";
|
||||
import * as messages from "../pure/legacy-messages";
|
||||
import * as newMessages from "../pure/new-messages";
|
||||
import * as qsClient from "./queryserver-client";
|
||||
import { asError, getErrorMessage } from "../pure/helpers-pure";
|
||||
} from "../../helpers";
|
||||
import { ProgressCallback } from "../../progress";
|
||||
import { QueryMetadata } from "../../pure/interface-types";
|
||||
import { extLogger, Logger } from "../../common";
|
||||
import * as messages from "../../pure/legacy-messages";
|
||||
import * as newMessages from "../../pure/new-messages";
|
||||
import * as qsClient from "./query-server-client";
|
||||
import { asError, getErrorMessage } from "../../pure/helpers-pure";
|
||||
import { compileDatabaseUpgradeSequence } from "./upgrades";
|
||||
import { QueryEvaluationInfo, QueryOutputDir } from "../run-queries-shared";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { CoreQueryResults, CoreQueryTarget } from "../queryRunner";
|
||||
import { Position } from "../pure/messages-shared";
|
||||
import { QueryEvaluationInfo, QueryOutputDir } from "../../run-queries-shared";
|
||||
import { redactableError } from "../../pure/errors";
|
||||
import { CoreQueryResults, CoreQueryTarget } from "../query-runner";
|
||||
import { Position } from "../../pure/messages-shared";
|
||||
|
||||
export async function compileQuery(
|
||||
qs: qsClient.QueryServerClient,
|
||||
@@ -3,16 +3,16 @@ import {
|
||||
getOnDiskWorkspaceFolders,
|
||||
showAndLogExceptionWithTelemetry,
|
||||
tmpDir,
|
||||
} from "../helpers";
|
||||
import { ProgressCallback, UserCancellationException } from "../progress";
|
||||
import { extLogger } from "../common";
|
||||
import * as messages from "../pure/legacy-messages";
|
||||
import * as qsClient from "./queryserver-client";
|
||||
} from "../../helpers";
|
||||
import { ProgressCallback, UserCancellationException } from "../../progress";
|
||||
import { extLogger } from "../../common";
|
||||
import * as messages from "../../pure/legacy-messages";
|
||||
import * as qsClient from "./query-server-client";
|
||||
import * as tmp from "tmp-promise";
|
||||
import { dirname } from "path";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { asError, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { DatabaseItem } from "../../databases/local-databases";
|
||||
import { asError, getErrorMessage } from "../../pure/helpers-pure";
|
||||
import { redactableError } from "../../pure/errors";
|
||||
|
||||
/**
|
||||
* Maximum number of lines to include from database upgrade message,
|
||||
165
extensions/ql-vscode/src/query-server/new-query-runner.ts
Normal file
165
extensions/ql-vscode/src/query-server/new-query-runner.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { CancellationToken } from "vscode";
|
||||
import { ProgressCallback, UserCancellationException } from "../progress";
|
||||
import { DatabaseItem } from "../databases/local-databases";
|
||||
import {
|
||||
clearCache,
|
||||
ClearCacheParams,
|
||||
clearPackCache,
|
||||
deregisterDatabases,
|
||||
registerDatabases,
|
||||
upgradeDatabase,
|
||||
} from "../pure/new-messages";
|
||||
import { CoreQueryResults, CoreQueryTarget, QueryRunner } from "./query-runner";
|
||||
import { QueryServerClient } from "./query-server-client";
|
||||
import { compileAndRunQueryAgainstDatabaseCore } from "./run-queries";
|
||||
import * as vscode from "vscode";
|
||||
import { getOnDiskWorkspaceFolders } from "../helpers";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { Logger } from "../common";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
|
||||
export class NewQueryRunner extends QueryRunner {
|
||||
constructor(public readonly qs: QueryServerClient) {
|
||||
super();
|
||||
}
|
||||
|
||||
get cliServer(): CodeQLCliServer {
|
||||
return this.qs.cliServer;
|
||||
}
|
||||
|
||||
get customLogDirectory(): string | undefined {
|
||||
return this.qs.config.customLogDirectory;
|
||||
}
|
||||
|
||||
get logger(): Logger {
|
||||
return this.qs.logger;
|
||||
}
|
||||
|
||||
async restartQueryServer(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> {
|
||||
await this.qs.restartQueryServer(progress, token);
|
||||
}
|
||||
|
||||
onStart(
|
||||
callBack: (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
) => Promise<void>,
|
||||
) {
|
||||
this.qs.onDidStartQueryServer(callBack);
|
||||
}
|
||||
|
||||
async clearCacheInDatabase(
|
||||
dbItem: DatabaseItem,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> {
|
||||
if (dbItem.contents === undefined) {
|
||||
throw new Error("Can't clear the cache in an invalid database.");
|
||||
}
|
||||
|
||||
const db = dbItem.databaseUri.fsPath;
|
||||
const params: ClearCacheParams = {
|
||||
dryRun: false,
|
||||
db,
|
||||
};
|
||||
await this.qs.sendRequest(clearCache, params, token, progress);
|
||||
}
|
||||
|
||||
public async compileAndRunQueryAgainstDatabaseCore(
|
||||
dbPath: string,
|
||||
query: CoreQueryTarget,
|
||||
additionalPacks: string[],
|
||||
extensionPacks: string[] | undefined,
|
||||
generateEvalLog: boolean,
|
||||
outputDir: QueryOutputDir,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
templates: Record<string, string> | undefined,
|
||||
logger: Logger,
|
||||
): Promise<CoreQueryResults> {
|
||||
return await compileAndRunQueryAgainstDatabaseCore(
|
||||
this.qs,
|
||||
dbPath,
|
||||
query,
|
||||
generateEvalLog,
|
||||
additionalPacks,
|
||||
extensionPacks,
|
||||
outputDir,
|
||||
progress,
|
||||
token,
|
||||
templates,
|
||||
logger,
|
||||
);
|
||||
}
|
||||
|
||||
async deregisterDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
dbItem: DatabaseItem,
|
||||
): Promise<void> {
|
||||
if (dbItem.contents) {
|
||||
const databases: string[] = [dbItem.databaseUri.fsPath];
|
||||
await this.qs.sendRequest(
|
||||
deregisterDatabases,
|
||||
{ databases },
|
||||
token,
|
||||
progress,
|
||||
);
|
||||
}
|
||||
}
|
||||
async registerDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
dbItem: DatabaseItem,
|
||||
): Promise<void> {
|
||||
if (dbItem.contents) {
|
||||
const databases: string[] = [dbItem.databaseUri.fsPath];
|
||||
await this.qs.sendRequest(
|
||||
registerDatabases,
|
||||
{ databases },
|
||||
token,
|
||||
progress,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async clearPackCache(): Promise<void> {
|
||||
await this.qs.sendRequest(clearPackCache, {});
|
||||
}
|
||||
|
||||
async upgradeDatabaseExplicit(
|
||||
dbItem: DatabaseItem,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> {
|
||||
const yesItem = { title: "Yes", isCloseAffordance: false };
|
||||
const noItem = { title: "No", isCloseAffordance: true };
|
||||
const dialogOptions: vscode.MessageItem[] = [yesItem, noItem];
|
||||
|
||||
const message = `Should the database ${dbItem.databaseUri.fsPath} be destructively upgraded?\n\nThis should not be necessary to run queries
|
||||
as we will non-destructively update it anyway.`;
|
||||
const chosenItem = await vscode.window.showInformationMessage(
|
||||
message,
|
||||
{ modal: true },
|
||||
...dialogOptions,
|
||||
);
|
||||
|
||||
if (chosenItem !== yesItem) {
|
||||
throw new UserCancellationException(
|
||||
"User cancelled the database upgrade.",
|
||||
);
|
||||
}
|
||||
await this.qs.sendRequest(
|
||||
upgradeDatabase,
|
||||
{
|
||||
db: dbItem.databaseUri.fsPath,
|
||||
additionalPacks: getOnDiskWorkspaceFolders(),
|
||||
},
|
||||
token,
|
||||
progress,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,74 +1,72 @@
|
||||
import { CancellationToken } from "vscode";
|
||||
import { ProgressCallback, UserCancellationException } from "../progress";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import {
|
||||
clearCache,
|
||||
ClearCacheParams,
|
||||
clearPackCache,
|
||||
deregisterDatabases,
|
||||
registerDatabases,
|
||||
upgradeDatabase,
|
||||
} from "../pure/new-messages";
|
||||
import { CoreQueryResults, CoreQueryTarget, QueryRunner } from "../queryRunner";
|
||||
import { QueryServerClient } from "./queryserver-client";
|
||||
import { compileAndRunQueryAgainstDatabaseCore } from "./run-queries";
|
||||
import * as vscode from "vscode";
|
||||
import { getOnDiskWorkspaceFolders } from "../helpers";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { Logger } from "../common";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import { DatabaseItem } from "../databases/local-databases";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
import { Position, QueryResultType } from "../pure/new-messages";
|
||||
import { BaseLogger, Logger } from "../common";
|
||||
import { basename, join } from "path";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export class NewQueryRunner extends QueryRunner {
|
||||
constructor(public readonly qs: QueryServerClient) {
|
||||
super();
|
||||
}
|
||||
export interface CoreQueryTarget {
|
||||
/** The full path to the query. */
|
||||
queryPath: string;
|
||||
/**
|
||||
* Optional position of text to be used as QuickEval target. This need not be in the same file as
|
||||
* `query`.
|
||||
*/
|
||||
quickEvalPosition?: Position;
|
||||
}
|
||||
|
||||
get cliServer(): CodeQLCliServer {
|
||||
return this.qs.cliServer;
|
||||
}
|
||||
export interface CoreQueryResults {
|
||||
readonly resultType: QueryResultType;
|
||||
readonly message: string | undefined;
|
||||
readonly evaluationTime: number;
|
||||
}
|
||||
|
||||
get customLogDirectory(): string | undefined {
|
||||
return this.qs.config.customLogDirectory;
|
||||
}
|
||||
export interface CoreQueryRun {
|
||||
readonly queryTarget: CoreQueryTarget;
|
||||
readonly dbPath: string;
|
||||
readonly id: string;
|
||||
readonly outputDir: QueryOutputDir;
|
||||
|
||||
get logger(): Logger {
|
||||
return this.qs.logger;
|
||||
}
|
||||
|
||||
async restartQueryServer(
|
||||
evaluate(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> {
|
||||
await this.qs.restartQueryServer(progress, token);
|
||||
}
|
||||
logger: BaseLogger,
|
||||
): Promise<CoreCompletedQuery>;
|
||||
}
|
||||
|
||||
onStart(
|
||||
callBack: (
|
||||
/** Includes both the results of the query and the initial information from `CoreQueryRun`. */
|
||||
export type CoreCompletedQuery = CoreQueryResults &
|
||||
Omit<CoreQueryRun, "evaluate">;
|
||||
|
||||
export abstract class QueryRunner {
|
||||
abstract restartQueryServer(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void>;
|
||||
|
||||
abstract cliServer: CodeQLCliServer;
|
||||
abstract customLogDirectory: string | undefined;
|
||||
abstract logger: Logger;
|
||||
|
||||
abstract onStart(
|
||||
arg0: (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
) => Promise<void>,
|
||||
) {
|
||||
this.qs.onDidStartQueryServer(callBack);
|
||||
}
|
||||
|
||||
async clearCacheInDatabase(
|
||||
): void;
|
||||
abstract clearCacheInDatabase(
|
||||
dbItem: DatabaseItem,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> {
|
||||
if (dbItem.contents === undefined) {
|
||||
throw new Error("Can't clear the cache in an invalid database.");
|
||||
}
|
||||
): Promise<void>;
|
||||
|
||||
const db = dbItem.databaseUri.fsPath;
|
||||
const params: ClearCacheParams = {
|
||||
dryRun: false,
|
||||
db,
|
||||
};
|
||||
await this.qs.sendRequest(clearCache, params, token, progress);
|
||||
}
|
||||
|
||||
public async compileAndRunQueryAgainstDatabaseCore(
|
||||
/**
|
||||
* Overridden in subclasses to evaluate the query via the query server and return the results.
|
||||
*/
|
||||
public abstract compileAndRunQueryAgainstDatabaseCore(
|
||||
dbPath: string,
|
||||
query: CoreQueryTarget,
|
||||
additionalPacks: string[],
|
||||
@@ -78,88 +76,76 @@ export class NewQueryRunner extends QueryRunner {
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
templates: Record<string, string> | undefined,
|
||||
logger: Logger,
|
||||
): Promise<CoreQueryResults> {
|
||||
return await compileAndRunQueryAgainstDatabaseCore(
|
||||
this.qs,
|
||||
logger: BaseLogger,
|
||||
): Promise<CoreQueryResults>;
|
||||
|
||||
abstract deregisterDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
dbItem: DatabaseItem,
|
||||
): Promise<void>;
|
||||
|
||||
abstract registerDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
dbItem: DatabaseItem,
|
||||
): Promise<void>;
|
||||
|
||||
abstract upgradeDatabaseExplicit(
|
||||
dbItem: DatabaseItem,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void>;
|
||||
|
||||
abstract clearPackCache(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Create a `CoreQueryRun` object. This creates an object whose `evaluate()` function can be
|
||||
* called to actually evaluate the query. The returned object also contains information about the
|
||||
* query evaluation that is known even before evaluation starts, including the unique ID of the
|
||||
* evaluation and the path to its output directory.
|
||||
*/
|
||||
public createQueryRun(
|
||||
dbPath: string,
|
||||
query: CoreQueryTarget,
|
||||
generateEvalLog: boolean,
|
||||
additionalPacks: string[],
|
||||
extensionPacks: string[] | undefined,
|
||||
queryStorageDir: string,
|
||||
id = `${basename(query.queryPath)}-${nanoid()}`,
|
||||
templates: Record<string, string> | undefined,
|
||||
): CoreQueryRun {
|
||||
const outputDir = new QueryOutputDir(join(queryStorageDir, id));
|
||||
|
||||
return {
|
||||
queryTarget: query,
|
||||
dbPath,
|
||||
query,
|
||||
generateEvalLog,
|
||||
additionalPacks,
|
||||
extensionPacks,
|
||||
id,
|
||||
outputDir,
|
||||
progress,
|
||||
token,
|
||||
templates,
|
||||
logger,
|
||||
);
|
||||
}
|
||||
|
||||
async deregisterDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
dbItem: DatabaseItem,
|
||||
): Promise<void> {
|
||||
if (dbItem.contents) {
|
||||
const databases: string[] = [dbItem.databaseUri.fsPath];
|
||||
await this.qs.sendRequest(
|
||||
deregisterDatabases,
|
||||
{ databases },
|
||||
token,
|
||||
progress,
|
||||
);
|
||||
}
|
||||
}
|
||||
async registerDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
dbItem: DatabaseItem,
|
||||
): Promise<void> {
|
||||
if (dbItem.contents) {
|
||||
const databases: string[] = [dbItem.databaseUri.fsPath];
|
||||
await this.qs.sendRequest(
|
||||
registerDatabases,
|
||||
{ databases },
|
||||
token,
|
||||
progress,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async clearPackCache(): Promise<void> {
|
||||
await this.qs.sendRequest(clearPackCache, {});
|
||||
}
|
||||
|
||||
async upgradeDatabaseExplicit(
|
||||
dbItem: DatabaseItem,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> {
|
||||
const yesItem = { title: "Yes", isCloseAffordance: false };
|
||||
const noItem = { title: "No", isCloseAffordance: true };
|
||||
const dialogOptions: vscode.MessageItem[] = [yesItem, noItem];
|
||||
|
||||
const message = `Should the database ${dbItem.databaseUri.fsPath} be destructively upgraded?\n\nThis should not be necessary to run queries
|
||||
as we will non-destructively update it anyway.`;
|
||||
const chosenItem = await vscode.window.showInformationMessage(
|
||||
message,
|
||||
{ modal: true },
|
||||
...dialogOptions,
|
||||
);
|
||||
|
||||
if (chosenItem !== yesItem) {
|
||||
throw new UserCancellationException(
|
||||
"User cancelled the database upgrade.",
|
||||
);
|
||||
}
|
||||
await this.qs.sendRequest(
|
||||
upgradeDatabase,
|
||||
{
|
||||
db: dbItem.databaseUri.fsPath,
|
||||
additionalPacks: getOnDiskWorkspaceFolders(),
|
||||
evaluate: async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
logger: BaseLogger,
|
||||
): Promise<CoreCompletedQuery> => {
|
||||
return {
|
||||
id,
|
||||
outputDir,
|
||||
dbPath,
|
||||
queryTarget: query,
|
||||
...(await this.compileAndRunQueryAgainstDatabaseCore(
|
||||
dbPath,
|
||||
query,
|
||||
additionalPacks,
|
||||
extensionPacks,
|
||||
generateEvalLog,
|
||||
outputDir,
|
||||
progress,
|
||||
token,
|
||||
templates,
|
||||
logger,
|
||||
)),
|
||||
};
|
||||
},
|
||||
token,
|
||||
progress,
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
WithProgressId,
|
||||
} from "../pure/new-messages";
|
||||
import { ProgressCallback, ProgressTask } from "../progress";
|
||||
import { ServerProcess } from "../json-rpc-server";
|
||||
import { ServerProcess } from "./server-process";
|
||||
import { App } from "../common/app";
|
||||
|
||||
type ServerOpts = {
|
||||
@@ -2,8 +2,8 @@ import { CancellationToken } from "vscode";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import * as messages from "../pure/new-messages";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
import * as qsClient from "./queryserver-client";
|
||||
import { CoreQueryResults, CoreQueryTarget } from "../queryRunner";
|
||||
import * as qsClient from "./query-server-client";
|
||||
import { CoreQueryResults, CoreQueryTarget } from "./query-runner";
|
||||
import { Logger } from "../common";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Logger } from "./common";
|
||||
import { Logger } from "../common";
|
||||
import * as cp from "child_process";
|
||||
import { Disposable } from "vscode";
|
||||
import { MessageConnection } from "vscode-jsonrpc";
|
||||
@@ -1,151 +0,0 @@
|
||||
import { CancellationToken } from "vscode";
|
||||
import { CodeQLCliServer } from "./cli";
|
||||
import { ProgressCallback } from "./progress";
|
||||
import { DatabaseItem } from "./local-databases";
|
||||
import { QueryOutputDir } from "./run-queries-shared";
|
||||
import { Position, QueryResultType } from "./pure/new-messages";
|
||||
import { BaseLogger, Logger } from "./common";
|
||||
import { basename, join } from "path";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export interface CoreQueryTarget {
|
||||
/** The full path to the query. */
|
||||
queryPath: string;
|
||||
/**
|
||||
* Optional position of text to be used as QuickEval target. This need not be in the same file as
|
||||
* `query`.
|
||||
*/
|
||||
quickEvalPosition?: Position;
|
||||
}
|
||||
|
||||
export interface CoreQueryResults {
|
||||
readonly resultType: QueryResultType;
|
||||
readonly message: string | undefined;
|
||||
readonly evaluationTime: number;
|
||||
}
|
||||
|
||||
export interface CoreQueryRun {
|
||||
readonly queryTarget: CoreQueryTarget;
|
||||
readonly dbPath: string;
|
||||
readonly id: string;
|
||||
readonly outputDir: QueryOutputDir;
|
||||
|
||||
evaluate(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
logger: BaseLogger,
|
||||
): Promise<CoreCompletedQuery>;
|
||||
}
|
||||
|
||||
/** Includes both the results of the query and the initial information from `CoreQueryRun`. */
|
||||
export type CoreCompletedQuery = CoreQueryResults &
|
||||
Omit<CoreQueryRun, "evaluate">;
|
||||
|
||||
export abstract class QueryRunner {
|
||||
abstract restartQueryServer(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void>;
|
||||
|
||||
abstract cliServer: CodeQLCliServer;
|
||||
abstract customLogDirectory: string | undefined;
|
||||
abstract logger: Logger;
|
||||
|
||||
abstract onStart(
|
||||
arg0: (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
) => Promise<void>,
|
||||
): void;
|
||||
abstract clearCacheInDatabase(
|
||||
dbItem: DatabaseItem,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Overridden in subclasses to evaluate the query via the query server and return the results.
|
||||
*/
|
||||
public abstract compileAndRunQueryAgainstDatabaseCore(
|
||||
dbPath: string,
|
||||
query: CoreQueryTarget,
|
||||
additionalPacks: string[],
|
||||
extensionPacks: string[] | undefined,
|
||||
generateEvalLog: boolean,
|
||||
outputDir: QueryOutputDir,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
templates: Record<string, string> | undefined,
|
||||
logger: BaseLogger,
|
||||
): Promise<CoreQueryResults>;
|
||||
|
||||
abstract deregisterDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
dbItem: DatabaseItem,
|
||||
): Promise<void>;
|
||||
|
||||
abstract registerDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
dbItem: DatabaseItem,
|
||||
): Promise<void>;
|
||||
|
||||
abstract upgradeDatabaseExplicit(
|
||||
dbItem: DatabaseItem,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void>;
|
||||
|
||||
abstract clearPackCache(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Create a `CoreQueryRun` object. This creates an object whose `evaluate()` function can be
|
||||
* called to actually evaluate the query. The returned object also contains information about the
|
||||
* query evaluation that is known even before evaluation starts, including the unique ID of the
|
||||
* evaluation and the path to its output directory.
|
||||
*/
|
||||
public createQueryRun(
|
||||
dbPath: string,
|
||||
query: CoreQueryTarget,
|
||||
generateEvalLog: boolean,
|
||||
additionalPacks: string[],
|
||||
extensionPacks: string[] | undefined,
|
||||
queryStorageDir: string,
|
||||
id = `${basename(query.queryPath)}-${nanoid()}`,
|
||||
templates: Record<string, string> | undefined,
|
||||
): CoreQueryRun {
|
||||
const outputDir = new QueryOutputDir(join(queryStorageDir, id));
|
||||
|
||||
return {
|
||||
queryTarget: query,
|
||||
dbPath,
|
||||
id,
|
||||
outputDir,
|
||||
evaluate: async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
logger: BaseLogger,
|
||||
): Promise<CoreCompletedQuery> => {
|
||||
return {
|
||||
id,
|
||||
outputDir,
|
||||
dbPath,
|
||||
queryTarget: query,
|
||||
...(await this.compileAndRunQueryAgainstDatabaseCore(
|
||||
dbPath,
|
||||
query,
|
||||
additionalPacks,
|
||||
extensionPacks,
|
||||
generateEvalLog,
|
||||
outputDir,
|
||||
progress,
|
||||
token,
|
||||
templates,
|
||||
logger,
|
||||
)),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,11 @@ import {
|
||||
readdir,
|
||||
} from "fs-extra";
|
||||
import { ensureMetadataIsComplete, InitialQueryInfo } from "./query-results";
|
||||
import { isQuickQueryPath } from "./quick-query";
|
||||
import { isQuickQueryPath } from "./local-queries";
|
||||
import { nanoid } from "nanoid";
|
||||
import { CodeQLCliServer } from "./cli";
|
||||
import { SELECT_QUERY_NAME } from "./contextual/locationFinder";
|
||||
import { DatabaseManager } from "./local-databases";
|
||||
import { SELECT_QUERY_NAME } from "./language-support";
|
||||
import { DatabaseManager } from "./databases/local-databases";
|
||||
import { DecodedBqrsChunk, EntityValue } from "./pure/bqrs-cli-types";
|
||||
import { BaseLogger, extLogger } from "./common";
|
||||
import { generateSummarySymbolsFile } from "./log-insights/summary-parser";
|
||||
|
||||
@@ -11,21 +11,29 @@ import {
|
||||
} from "./helpers";
|
||||
import { getErrorMessage } from "./pure/helpers-pure";
|
||||
import { QlPackGenerator } from "./qlpack-generator";
|
||||
import { DatabaseItem, DatabaseManager } from "./local-databases";
|
||||
import { DatabaseItem, DatabaseManager } from "./databases/local-databases";
|
||||
import { ProgressCallback, UserCancellationException } from "./progress";
|
||||
import { askForGitHubRepo, downloadGitHubDatabase } from "./databaseFetcher";
|
||||
import { existsSync } from "fs";
|
||||
import {
|
||||
askForGitHubRepo,
|
||||
downloadGitHubDatabase,
|
||||
} from "./databases/database-fetcher";
|
||||
import {
|
||||
getSkeletonWizardFolder,
|
||||
isCodespacesTemplate,
|
||||
setSkeletonWizardFolder,
|
||||
} from "./config";
|
||||
import { existsSync } from "fs-extra";
|
||||
|
||||
type QueryLanguagesToDatabaseMap = Record<string, string>;
|
||||
|
||||
export const QUERY_LANGUAGE_TO_DATABASE_REPO: QueryLanguagesToDatabaseMap = {
|
||||
cpp: "protocolbuffers/protobuf",
|
||||
csharp: "dotnet/efcore",
|
||||
go: "evanw/esbuild",
|
||||
java: "google/guava",
|
||||
javascript: "facebook/react",
|
||||
cpp: "google/brotli",
|
||||
csharp: "restsharp/RestSharp",
|
||||
go: "spf13/cobra",
|
||||
java: "projectlombok/lombok",
|
||||
javascript: "d3/d3",
|
||||
python: "pallets/flask",
|
||||
ruby: "rails/rails",
|
||||
ruby: "jekyll/jekyll",
|
||||
swift: "Alamofire/Alamofire",
|
||||
};
|
||||
|
||||
@@ -55,7 +63,7 @@ export class SkeletonQueryWizard {
|
||||
return;
|
||||
}
|
||||
|
||||
this.qlPackStoragePath = getFirstWorkspaceFolder();
|
||||
this.qlPackStoragePath = await this.determineStoragePath();
|
||||
|
||||
const skeletonPackAlreadyExists =
|
||||
existsSync(join(this.qlPackStoragePath, this.folderName)) ||
|
||||
@@ -64,15 +72,14 @@ export class SkeletonQueryWizard {
|
||||
if (skeletonPackAlreadyExists) {
|
||||
// just create a new example query file in skeleton QL pack
|
||||
await this.createExampleFile();
|
||||
// select existing database for language
|
||||
await this.selectExistingDatabase();
|
||||
} else {
|
||||
// generate a new skeleton QL pack with query file
|
||||
await this.createQlPack();
|
||||
// download database based on language and select it
|
||||
await this.downloadDatabase();
|
||||
}
|
||||
|
||||
// select existing database for language or download a new one
|
||||
await this.selectOrDownloadDatabase();
|
||||
|
||||
// open a query file
|
||||
|
||||
try {
|
||||
@@ -98,6 +105,38 @@ export class SkeletonQueryWizard {
|
||||
});
|
||||
}
|
||||
|
||||
public async determineStoragePath() {
|
||||
const firstStorageFolder = getFirstWorkspaceFolder();
|
||||
|
||||
if (isCodespacesTemplate()) {
|
||||
return firstStorageFolder;
|
||||
}
|
||||
|
||||
let storageFolder = getSkeletonWizardFolder();
|
||||
|
||||
if (storageFolder === undefined || !existsSync(storageFolder)) {
|
||||
storageFolder = await Window.showInputBox({
|
||||
title:
|
||||
"Please choose a folder in which to create your new query pack. You can change this in the extension settings.",
|
||||
value: firstStorageFolder,
|
||||
ignoreFocusOut: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (storageFolder === undefined) {
|
||||
throw new UserCancellationException("No storage folder entered.");
|
||||
}
|
||||
|
||||
if (!existsSync(storageFolder)) {
|
||||
throw new UserCancellationException(
|
||||
"Invalid folder. Must be a folder that already exists.",
|
||||
);
|
||||
}
|
||||
|
||||
await setSkeletonWizardFolder(storageFolder);
|
||||
return storageFolder;
|
||||
}
|
||||
|
||||
private async chooseLanguage() {
|
||||
this.progress({
|
||||
message: "Choose language",
|
||||
@@ -105,7 +144,7 @@ export class SkeletonQueryWizard {
|
||||
maxStep: 3,
|
||||
});
|
||||
|
||||
return await askForLanguage(this.cliServer, false);
|
||||
return await askForLanguage(this.cliServer, true);
|
||||
}
|
||||
|
||||
private async createQlPack() {
|
||||
@@ -216,7 +255,7 @@ export class SkeletonQueryWizard {
|
||||
);
|
||||
}
|
||||
|
||||
private async selectExistingDatabase() {
|
||||
private async selectOrDownloadDatabase() {
|
||||
if (this.language === undefined) {
|
||||
throw new Error("Language is undefined");
|
||||
}
|
||||
@@ -225,65 +264,83 @@ export class SkeletonQueryWizard {
|
||||
throw new Error("QL Pack storage path is undefined");
|
||||
}
|
||||
|
||||
const databaseNwo = QUERY_LANGUAGE_TO_DATABASE_REPO[this.language];
|
||||
|
||||
const existingDatabaseItem = await this.findDatabaseItemByNwo(
|
||||
this.language,
|
||||
databaseNwo,
|
||||
this.databaseManager.databaseItems,
|
||||
);
|
||||
const existingDatabaseItem =
|
||||
await SkeletonQueryWizard.findExistingDatabaseItem(
|
||||
this.language,
|
||||
this.databaseManager.databaseItems,
|
||||
);
|
||||
|
||||
if (existingDatabaseItem) {
|
||||
// select the found database
|
||||
await this.databaseManager.setCurrentDatabaseItem(existingDatabaseItem);
|
||||
} else {
|
||||
const sameLanguageDatabaseItem = await this.findDatabaseItemByLanguage(
|
||||
this.language,
|
||||
this.databaseManager.databaseItems,
|
||||
);
|
||||
|
||||
if (sameLanguageDatabaseItem) {
|
||||
// select the found database
|
||||
await this.databaseManager.setCurrentDatabaseItem(
|
||||
sameLanguageDatabaseItem,
|
||||
);
|
||||
} else {
|
||||
// download new database and select it
|
||||
await this.downloadDatabase();
|
||||
}
|
||||
// download new database and select it
|
||||
await this.downloadDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
public async findDatabaseItemByNwo(
|
||||
public static async findDatabaseItemByNwo(
|
||||
language: string,
|
||||
databaseNwo: string,
|
||||
databaseItems: readonly DatabaseItem[],
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
const dbItems = databaseItems || [];
|
||||
const dbs = dbItems.filter(
|
||||
(db) =>
|
||||
db.language === language &&
|
||||
db.name === databaseNwo &&
|
||||
db.error === undefined,
|
||||
const dbs = databaseItems.filter(
|
||||
(db) => db.language === language && db.name === databaseNwo,
|
||||
);
|
||||
|
||||
if (dbs.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return dbs[0];
|
||||
return dbs.pop();
|
||||
}
|
||||
|
||||
public async findDatabaseItemByLanguage(
|
||||
public static async findDatabaseItemByLanguage(
|
||||
language: string,
|
||||
databaseItems: readonly DatabaseItem[],
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
const dbItems = databaseItems || [];
|
||||
const dbs = dbItems.filter(
|
||||
(db) => db.language === language && db.error === undefined,
|
||||
const dbs = databaseItems.filter((db) => db.language === language);
|
||||
|
||||
return dbs.pop();
|
||||
}
|
||||
|
||||
public static async findExistingDatabaseItem(
|
||||
language: string,
|
||||
databaseItems: readonly DatabaseItem[],
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
const defaultDatabaseNwo = QUERY_LANGUAGE_TO_DATABASE_REPO[language];
|
||||
|
||||
const dbItems = await SkeletonQueryWizard.sortDatabaseItemsByDateAdded(
|
||||
databaseItems,
|
||||
);
|
||||
if (dbs.length === 0) {
|
||||
return undefined;
|
||||
|
||||
const defaultDatabaseItem = await SkeletonQueryWizard.findDatabaseItemByNwo(
|
||||
language,
|
||||
defaultDatabaseNwo,
|
||||
dbItems,
|
||||
);
|
||||
|
||||
if (defaultDatabaseItem !== undefined) {
|
||||
return defaultDatabaseItem;
|
||||
}
|
||||
return dbs[0];
|
||||
|
||||
return await SkeletonQueryWizard.findDatabaseItemByLanguage(
|
||||
language,
|
||||
dbItems,
|
||||
);
|
||||
}
|
||||
|
||||
public static async sortDatabaseItemsByDateAdded(
|
||||
databaseItems: readonly DatabaseItem[],
|
||||
) {
|
||||
const validDbItems = databaseItems.filter((db) => db.error === undefined);
|
||||
|
||||
return validDbItems.sort((a, b) => {
|
||||
if (a.dateAdded === undefined) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (b.dateAdded === undefined) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return a.dateAdded - b.dateAdded;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,22 @@ const Template: ComponentStory<typeof DataExtensionsEditorComponent> = (
|
||||
|
||||
export const DataExtensionsEditor = Template.bind({});
|
||||
DataExtensionsEditor.args = {
|
||||
initialExtensionPackName: "codeql/sql2o-models",
|
||||
initialModelFilename:
|
||||
"/home/user/vscode-codeql-starter/codeql-custom-queries-java/sql2o/models/sql2o.yml",
|
||||
initialViewState: {
|
||||
extensionPackModelFile: {
|
||||
extensionPack: {
|
||||
path: "/home/user/vscode-codeql-starter/codeql-custom-queries-java/sql2o",
|
||||
yamlPath:
|
||||
"/home/user/vscode-codeql-starter/codeql-custom-queries-java/sql2o/codeql-pack.yml",
|
||||
name: "codeql/sql2o-models",
|
||||
version: "0.0.0",
|
||||
extensionTargets: {},
|
||||
dataExtensions: [],
|
||||
},
|
||||
filename:
|
||||
"/home/user/vscode-codeql-starter/codeql-custom-queries-java/sql2o/models/sql2o.yml",
|
||||
},
|
||||
modelFileExists: true,
|
||||
},
|
||||
initialExternalApiUsages: [
|
||||
{
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
@@ -200,13 +213,13 @@ DataExtensionsEditor.args = {
|
||||
},
|
||||
"org.sql2o.Connection#createQuery(String)": {
|
||||
type: "summary",
|
||||
input: "Argument[-1]",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "taint",
|
||||
},
|
||||
"org.sql2o.Sql2o#open()": {
|
||||
type: "summary",
|
||||
input: "Argument[-1]",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "taint",
|
||||
},
|
||||
|
||||
@@ -47,7 +47,7 @@ MethodRow.args = {
|
||||
},
|
||||
modeledMethod: {
|
||||
type: "summary",
|
||||
input: "Argument[-1]",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "taint",
|
||||
},
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { ComponentMeta } from "@storybook/react";
|
||||
|
||||
import { RepositoriesFilter as RepositoriesFilterComponent } from "../../view/variant-analysis/RepositoriesFilter";
|
||||
import { FilterKey } from "../../pure/variant-analysis-filter-sort";
|
||||
|
||||
export default {
|
||||
title: "Variant Analysis/Repositories Filter",
|
||||
component: RepositoriesFilterComponent,
|
||||
argTypes: {
|
||||
value: {
|
||||
control: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof RepositoriesFilterComponent>;
|
||||
|
||||
export const RepositoriesFilter = () => {
|
||||
const [value, setValue] = useState(FilterKey.All);
|
||||
|
||||
return <RepositoriesFilterComponent value={value} onChange={setValue} />;
|
||||
};
|
||||
@@ -19,7 +19,7 @@ export default {
|
||||
} as ComponentMeta<typeof RepositoriesSortComponent>;
|
||||
|
||||
export const RepositoriesSort = () => {
|
||||
const [value, setValue] = useState(SortKey.Name);
|
||||
const [value, setValue] = useState(SortKey.Alphabetically);
|
||||
|
||||
return <RepositoriesSortComponent value={value} onChange={setValue} />;
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@ import { extLogger } from "./common";
|
||||
import { UserCancellationException } from "./progress";
|
||||
import { showBinaryChoiceWithUrlDialog } from "./helpers";
|
||||
import { RedactableError } from "./pure/errors";
|
||||
import { SemVer } from "semver";
|
||||
|
||||
// Key is injected at build time through the APP_INSIGHTS_KEY environment variable.
|
||||
const key = "REPLACE-APP-INSIGHTS-KEY";
|
||||
@@ -51,11 +52,15 @@ const baseDataPropertiesToRemove = [
|
||||
"common.vscodesessionid",
|
||||
];
|
||||
|
||||
const NOT_SET_CLI_VERSION = "not-set";
|
||||
|
||||
export class TelemetryListener extends ConfigListener {
|
||||
static relevantSettings = [ENABLE_TELEMETRY, CANARY_FEATURES];
|
||||
|
||||
private reporter?: TelemetryReporter;
|
||||
|
||||
private cliVersionStr = NOT_SET_CLI_VERSION;
|
||||
|
||||
constructor(
|
||||
private readonly id: string,
|
||||
private readonly version: string,
|
||||
@@ -163,6 +168,7 @@ export class TelemetryListener extends ConfigListener {
|
||||
name,
|
||||
status,
|
||||
isCanary: isCanary().toString(),
|
||||
cliVersion: this.cliVersionStr,
|
||||
},
|
||||
{ executionTime },
|
||||
);
|
||||
@@ -178,6 +184,7 @@ export class TelemetryListener extends ConfigListener {
|
||||
{
|
||||
name,
|
||||
isCanary: isCanary().toString(),
|
||||
cliVersion: this.cliVersionStr,
|
||||
},
|
||||
{},
|
||||
);
|
||||
@@ -193,6 +200,7 @@ export class TelemetryListener extends ConfigListener {
|
||||
|
||||
const properties: { [key: string]: string } = {
|
||||
isCanary: isCanary().toString(),
|
||||
cliVersion: this.cliVersionStr,
|
||||
message: error.redactedMessage,
|
||||
...extraProperties,
|
||||
};
|
||||
@@ -241,6 +249,10 @@ export class TelemetryListener extends ConfigListener {
|
||||
return this.reporter;
|
||||
}
|
||||
|
||||
set cliVersion(version: SemVer | undefined) {
|
||||
this.cliVersionStr = version ? version.toString() : NOT_SET_CLI_VERSION;
|
||||
}
|
||||
|
||||
private disposeReporter() {
|
||||
if (this.reporter) {
|
||||
void this.reporter.dispose();
|
||||
@@ -265,7 +277,7 @@ export let telemetryListener: TelemetryListener | undefined;
|
||||
export async function initializeTelemetry(
|
||||
extension: Extension<any>,
|
||||
ctx: ExtensionContext,
|
||||
): Promise<void> {
|
||||
): Promise<TelemetryListener> {
|
||||
if (telemetryListener !== undefined) {
|
||||
throw new Error("Telemetry is already initialized");
|
||||
}
|
||||
@@ -279,4 +291,5 @@ export async function initializeTelemetry(
|
||||
// this is a particular problem during integration tests, which will hang if a modal popup is displayed.
|
||||
void telemetryListener.initialize();
|
||||
ctx.subscriptions.push(telemetryListener);
|
||||
return telemetryListener;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import { BaseLogger, LogOptions } from "./common";
|
||||
import { TestRunner } from "./test-runner";
|
||||
import { TestManagerBase } from "./test-manager-base";
|
||||
import { App } from "./common/app";
|
||||
import { isWorkspaceFolderOnDisk } from "./helpers";
|
||||
|
||||
/**
|
||||
* Returns the complete text content of the specified file. If there is an error reading the file,
|
||||
@@ -162,15 +163,22 @@ export class TestManager extends TestManagerBase {
|
||||
private startTrackingWorkspaceFolders(
|
||||
workspaceFolders: readonly WorkspaceFolder[],
|
||||
): void {
|
||||
for (const workspaceFolder of workspaceFolders) {
|
||||
const workspaceFolderHandler = new WorkspaceFolderHandler(
|
||||
workspaceFolder,
|
||||
this,
|
||||
this.cliServer,
|
||||
);
|
||||
this.track(workspaceFolderHandler);
|
||||
this.workspaceFolderHandlers.set(workspaceFolder, workspaceFolderHandler);
|
||||
}
|
||||
// Only track on-disk workspace folders, to avoid trying to run the CLI test discovery command
|
||||
// on random URIs.
|
||||
workspaceFolders
|
||||
.filter(isWorkspaceFolderOnDisk)
|
||||
.forEach((workspaceFolder) => {
|
||||
const workspaceFolderHandler = new WorkspaceFolderHandler(
|
||||
workspaceFolder,
|
||||
this,
|
||||
this.cliServer,
|
||||
);
|
||||
this.track(workspaceFolderHandler);
|
||||
this.workspaceFolderHandlers.set(
|
||||
workspaceFolder,
|
||||
workspaceFolderHandler,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/** Stop tracking tests in the specified workspace folders. */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CancellationToken, Uri } from "vscode";
|
||||
import { CodeQLCliServer, TestCompleted } from "./cli";
|
||||
import { DatabaseItem, DatabaseManager } from "./local-databases";
|
||||
import { DatabaseItem, DatabaseManager } from "./databases/local-databases";
|
||||
import {
|
||||
getOnDiskWorkspaceFolders,
|
||||
showAndLogExceptionWithTelemetry,
|
||||
|
||||
@@ -34,7 +34,7 @@ export const LastUpdated = ({ lastUpdated }: Props) => {
|
||||
return (
|
||||
<div>
|
||||
<IconContainer>
|
||||
<Codicon name="repo-push" label="Last updated" />
|
||||
<Codicon name="repo-push" label="Most recent commit" />
|
||||
</IconContainer>
|
||||
<Duration>{humanizeRelativeTime(date.getTime() - Date.now())}</Duration>
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,7 @@ import { calculateModeledPercentage } from "./modeled";
|
||||
import { LinkIconButton } from "../variant-analysis/LinkIconButton";
|
||||
import { basename } from "../common/path";
|
||||
import { ViewTitle } from "../common";
|
||||
import { DataExtensionEditorViewState } from "../../data-extensions-editor/shared/view-state";
|
||||
|
||||
const DataExtensionsEditorContainer = styled.div`
|
||||
margin-top: 1rem;
|
||||
@@ -31,6 +32,12 @@ const DetailsContainer = styled.div`
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const NonExistingModelFileContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 0.2em;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const EditorContainer = styled.div`
|
||||
margin-top: 1rem;
|
||||
`;
|
||||
@@ -47,24 +54,19 @@ const ProgressBar = styled.div<ProgressBarProps>`
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
initialExtensionPackName?: string;
|
||||
initialModelFilename?: string;
|
||||
initialViewState?: DataExtensionEditorViewState;
|
||||
initialExternalApiUsages?: ExternalApiUsage[];
|
||||
initialModeledMethods?: Record<string, ModeledMethod>;
|
||||
};
|
||||
|
||||
export function DataExtensionsEditor({
|
||||
initialExtensionPackName,
|
||||
initialModelFilename,
|
||||
initialViewState,
|
||||
initialExternalApiUsages = [],
|
||||
initialModeledMethods = {},
|
||||
}: Props): JSX.Element {
|
||||
const [extensionPackName, setExtensionPackName] = useState<
|
||||
string | undefined
|
||||
>(initialExtensionPackName);
|
||||
const [modelFilename, setModelFilename] = useState<string | undefined>(
|
||||
initialModelFilename,
|
||||
);
|
||||
const [viewState, setViewState] = useState<
|
||||
DataExtensionEditorViewState | undefined
|
||||
>(initialViewState);
|
||||
|
||||
const [externalApiUsages, setExternalApiUsages] = useState<
|
||||
ExternalApiUsage[]
|
||||
@@ -83,9 +85,8 @@ export function DataExtensionsEditor({
|
||||
if (evt.origin === window.origin) {
|
||||
const msg: ToDataExtensionsEditorMessage = evt.data;
|
||||
switch (msg.t) {
|
||||
case "setDataExtensionEditorInitialData":
|
||||
setExtensionPackName(msg.extensionPackName);
|
||||
setModelFilename(msg.modelFilename);
|
||||
case "setDataExtensionEditorViewState":
|
||||
setViewState(msg.viewState);
|
||||
break;
|
||||
case "setExternalApiUsages":
|
||||
setExternalApiUsages(msg.externalApiUsages);
|
||||
@@ -181,17 +182,27 @@ export function DataExtensionsEditor({
|
||||
<>
|
||||
<ViewTitle>Data extensions editor</ViewTitle>
|
||||
<DetailsContainer>
|
||||
{extensionPackName && (
|
||||
<LinkIconButton onClick={onOpenExtensionPackClick}>
|
||||
<span slot="start" className="codicon codicon-package"></span>
|
||||
{extensionPackName}
|
||||
</LinkIconButton>
|
||||
)}
|
||||
{modelFilename && (
|
||||
<LinkIconButton onClick={onOpenModelFileClick}>
|
||||
<span slot="start" className="codicon codicon-file-code"></span>
|
||||
{basename(modelFilename)}
|
||||
</LinkIconButton>
|
||||
{viewState?.extensionPackModelFile && (
|
||||
<>
|
||||
<LinkIconButton onClick={onOpenExtensionPackClick}>
|
||||
<span slot="start" className="codicon codicon-package"></span>
|
||||
{viewState.extensionPackModelFile.extensionPack.name}
|
||||
</LinkIconButton>
|
||||
{viewState.modelFileExists ? (
|
||||
<LinkIconButton onClick={onOpenModelFileClick}>
|
||||
<span
|
||||
slot="start"
|
||||
className="codicon codicon-file-code"
|
||||
></span>
|
||||
{basename(viewState.extensionPackModelFile.filename)}
|
||||
</LinkIconButton>
|
||||
) : (
|
||||
<NonExistingModelFileContainer>
|
||||
<span className="codicon codicon-file-code"></span>
|
||||
{basename(viewState.extensionPackModelFile.filename)}
|
||||
</NonExistingModelFileContainer>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div>{modeledPercentage.toFixed(2)}% modeled</div>
|
||||
<div>{unModeledPercentage.toFixed(2)}% unmodeled</div>
|
||||
|
||||
@@ -64,8 +64,8 @@ export const MethodRow = ({
|
||||
const target = e.target as HTMLSelectElement;
|
||||
|
||||
onChange(externalApiUsage, {
|
||||
// If there are no arguments, we will default to "this", which is Argument[-1]
|
||||
input: argumentsList.length === 0 ? "Argument[-1]" : "Argument[0]",
|
||||
// If there are no arguments, we will default to "Argument[this]"
|
||||
input: argumentsList.length === 0 ? "Argument[this]" : "Argument[0]",
|
||||
output: "ReturnType",
|
||||
kind: "value",
|
||||
...modeledMethod,
|
||||
@@ -167,9 +167,7 @@ export const MethodRow = ({
|
||||
{modeledMethod?.type &&
|
||||
["sink", "summary"].includes(modeledMethod?.type) && (
|
||||
<Dropdown value={modeledMethod?.input} onInput={handleInputInput}>
|
||||
<VSCodeOption value="Argument[-1]">
|
||||
Argument[-1]: this
|
||||
</VSCodeOption>
|
||||
<VSCodeOption value="Argument[this]">Argument[this]</VSCodeOption>
|
||||
{argumentsList.map((argument, index) => (
|
||||
<VSCodeOption key={argument} value={`Argument[${index}]`}>
|
||||
Argument[{index}]: {argument}
|
||||
@@ -183,9 +181,7 @@ export const MethodRow = ({
|
||||
["source", "summary"].includes(modeledMethod?.type) && (
|
||||
<Dropdown value={modeledMethod?.output} onInput={handleOutputInput}>
|
||||
<VSCodeOption value="ReturnValue">ReturnValue</VSCodeOption>
|
||||
<VSCodeOption value="Argument[-1]">
|
||||
Argument[-1]: this
|
||||
</VSCodeOption>
|
||||
<VSCodeOption value="Argument[this]">Argument[this]</VSCodeOption>
|
||||
{argumentsList.map((argument, index) => (
|
||||
<VSCodeOption key={argument} value={`Argument[${index}]`}>
|
||||
Argument[{index}]: {argument}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import * as React from "react";
|
||||
import { useCallback } from "react";
|
||||
import styled from "styled-components";
|
||||
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react";
|
||||
import { Codicon } from "../common";
|
||||
import { FilterKey } from "../../pure/variant-analysis-filter-sort";
|
||||
|
||||
const Dropdown = styled(VSCodeDropdown)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
value: FilterKey;
|
||||
onChange: (value: FilterKey) => void;
|
||||
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const RepositoriesFilter = ({ value, onChange, className }: Props) => {
|
||||
const handleInput = useCallback(
|
||||
(e: InputEvent) => {
|
||||
const target = e.target as HTMLSelectElement;
|
||||
|
||||
onChange(target.value as FilterKey);
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown value={value} onInput={handleInput} className={className}>
|
||||
<Codicon name="list-filter" label="Filter..." slot="indicator" />
|
||||
<VSCodeOption value={FilterKey.All}>All</VSCodeOption>
|
||||
<VSCodeOption value={FilterKey.WithResults}>With results</VSCodeOption>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
@@ -2,11 +2,13 @@ import * as React from "react";
|
||||
import { Dispatch, SetStateAction, useCallback } from "react";
|
||||
import styled from "styled-components";
|
||||
import {
|
||||
FilterKey,
|
||||
RepositoriesFilterSortState,
|
||||
SortKey,
|
||||
} from "../../pure/variant-analysis-filter-sort";
|
||||
import { RepositoriesSearch } from "./RepositoriesSearch";
|
||||
import { RepositoriesSort } from "./RepositoriesSort";
|
||||
import { RepositoriesFilter } from "./RepositoriesFilter";
|
||||
|
||||
type Props = {
|
||||
value: RepositoriesFilterSortState;
|
||||
@@ -25,6 +27,10 @@ const RepositoriesSearchColumn = styled(RepositoriesSearch)`
|
||||
flex: 3;
|
||||
`;
|
||||
|
||||
const RepositoriesFilterColumn = styled(RepositoriesFilter)`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const RepositoriesSortColumn = styled(RepositoriesSort)`
|
||||
flex: 1;
|
||||
`;
|
||||
@@ -40,6 +46,16 @@ export const RepositoriesSearchSortRow = ({ value, onChange }: Props) => {
|
||||
[onChange],
|
||||
);
|
||||
|
||||
const handleFilterKeyChange = useCallback(
|
||||
(filterKey: FilterKey) => {
|
||||
onChange((oldValue) => ({
|
||||
...oldValue,
|
||||
filterKey,
|
||||
}));
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
const handleSortKeyChange = useCallback(
|
||||
(sortKey: SortKey) => {
|
||||
onChange((oldValue) => ({
|
||||
@@ -56,6 +72,10 @@ export const RepositoriesSearchSortRow = ({ value, onChange }: Props) => {
|
||||
value={value.searchValue}
|
||||
onChange={handleSearchValueChange}
|
||||
/>
|
||||
<RepositoriesFilterColumn
|
||||
value={value.filterKey}
|
||||
onChange={handleFilterKeyChange}
|
||||
/>
|
||||
<RepositoriesSortColumn
|
||||
value={value.sortKey}
|
||||
onChange={handleSortKeyChange}
|
||||
|
||||
@@ -29,10 +29,14 @@ export const RepositoriesSort = ({ value, onChange, className }: Props) => {
|
||||
return (
|
||||
<Dropdown value={value} onInput={handleInput} className={className}>
|
||||
<Codicon name="sort-precedence" label="Sort..." slot="indicator" />
|
||||
<VSCodeOption value={SortKey.Name}>Name</VSCodeOption>
|
||||
<VSCodeOption value={SortKey.ResultsCount}>Results</VSCodeOption>
|
||||
<VSCodeOption value={SortKey.Stars}>Stars</VSCodeOption>
|
||||
<VSCodeOption value={SortKey.LastUpdated}>Last updated</VSCodeOption>
|
||||
<VSCodeOption value={SortKey.Alphabetically}>Alphabetically</VSCodeOption>
|
||||
<VSCodeOption value={SortKey.NumberOfResults}>
|
||||
Number of results
|
||||
</VSCodeOption>
|
||||
<VSCodeOption value={SortKey.Popularity}>Popularity</VSCodeOption>
|
||||
<VSCodeOption value={SortKey.MostRecentCommit}>
|
||||
Most recent commit
|
||||
</VSCodeOption>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -56,8 +56,8 @@ export const VariantAnalysisSkippedRepositoriesTab = ({
|
||||
}: VariantAnalysisSkippedRepositoriesTabProps) => {
|
||||
const repositories = useMemo(() => {
|
||||
return skippedRepositoryGroup.repositories
|
||||
?.filter((repo) => {
|
||||
return matchesFilter(repo, filterSortState);
|
||||
?.filter((repository) => {
|
||||
return matchesFilter({ repository }, filterSortState);
|
||||
})
|
||||
?.sort(compareRepository(filterSortState));
|
||||
}, [filterSortState, skippedRepositoryGroup.repositories]);
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import * as React from "react";
|
||||
import { render as reactRender, screen, waitFor } from "@testing-library/react";
|
||||
import {
|
||||
act,
|
||||
render as reactRender,
|
||||
screen,
|
||||
waitFor,
|
||||
} from "@testing-library/react";
|
||||
import {
|
||||
VariantAnalysisRepoStatus,
|
||||
VariantAnalysisScannedRepositoryDownloadStatus,
|
||||
@@ -34,7 +39,7 @@ describe(RepoRow.name, () => {
|
||||
screen.queryByRole("img", {
|
||||
// There should not be any icons, except for the icons which are always shown
|
||||
name: (name) =>
|
||||
!["expand", "stars count", "last updated"].includes(
|
||||
!["expand", "stars count", "most recent commit"].includes(
|
||||
name.toLowerCase(),
|
||||
),
|
||||
}),
|
||||
@@ -288,7 +293,7 @@ describe(RepoRow.name, () => {
|
||||
expect(screen.getByText("last month")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("img", {
|
||||
name: "Last updated",
|
||||
name: "Most recent commit",
|
||||
}),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
@@ -309,7 +314,7 @@ describe(RepoRow.name, () => {
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole("img", {
|
||||
name: "Last updated",
|
||||
name: "Most recent commit",
|
||||
}),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
@@ -319,11 +324,13 @@ describe(RepoRow.name, () => {
|
||||
status: VariantAnalysisRepoStatus.TimedOut,
|
||||
});
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole("button", {
|
||||
expanded: false,
|
||||
}),
|
||||
);
|
||||
await act(async () => {
|
||||
await userEvent.click(
|
||||
screen.getByRole("button", {
|
||||
expanded: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
screen.getByRole("button", {
|
||||
expanded: true,
|
||||
@@ -342,11 +349,13 @@ describe(RepoRow.name, () => {
|
||||
interpretedResults: [],
|
||||
});
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole("button", {
|
||||
expanded: false,
|
||||
}),
|
||||
);
|
||||
await act(async () => {
|
||||
await userEvent.click(
|
||||
screen.getByRole("button", {
|
||||
expanded: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.getByRole("button", {
|
||||
@@ -365,11 +374,13 @@ describe(RepoRow.name, () => {
|
||||
},
|
||||
});
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole("button", {
|
||||
expanded: false,
|
||||
}),
|
||||
);
|
||||
await act(async () => {
|
||||
await userEvent.click(
|
||||
screen.getByRole("button", {
|
||||
expanded: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
expect((window as any).vsCodeApi.postMessage).toHaveBeenCalledWith({
|
||||
t: "requestRepositoryResults",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { render as reactRender, screen } from "@testing-library/react";
|
||||
import { act, render as reactRender, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import {
|
||||
VariantAnalysisRepoStatus,
|
||||
@@ -155,11 +155,15 @@ describe(VariantAnalysisAnalyzedRepos.name, () => {
|
||||
expect(
|
||||
screen.queryByText("This is an empty block."),
|
||||
).not.toBeInTheDocument();
|
||||
await userEvent.click(
|
||||
screen.getByRole("button", {
|
||||
name: /octodemo\/hello-world-2/,
|
||||
}),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(
|
||||
screen.getByRole("button", {
|
||||
name: /octodemo\/hello-world-2/,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
expect(screen.getByText("This is an empty block.")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -187,7 +191,7 @@ describe(VariantAnalysisAnalyzedRepos.name, () => {
|
||||
render({
|
||||
filterSortState: {
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.Stars,
|
||||
sortKey: SortKey.Popularity,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -202,11 +206,11 @@ describe(VariantAnalysisAnalyzedRepos.name, () => {
|
||||
expect(rows[5]).toHaveTextContent("octodemo/hello-world-6");
|
||||
});
|
||||
|
||||
it("uses the results count sort key", async () => {
|
||||
it("uses the 'Number of results' sort key", async () => {
|
||||
render({
|
||||
filterSortState: {
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
sortKey: SortKey.NumberOfResults,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ describe(VariantAnalysisSkippedRepositoriesTab.name, () => {
|
||||
},
|
||||
filterSortState: {
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.Stars,
|
||||
sortKey: SortKey.Popularity,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -190,7 +190,7 @@ describe(VariantAnalysisSkippedRepositoriesTab.name, () => {
|
||||
expect(rows[2]).toHaveTextContent("octodemo/hello-galaxy");
|
||||
});
|
||||
|
||||
it("does not use the result count sort key", async () => {
|
||||
it("does not use the 'number of results' sort key", async () => {
|
||||
render({
|
||||
alertTitle: "No database",
|
||||
alertMessage:
|
||||
@@ -211,7 +211,7 @@ describe(VariantAnalysisSkippedRepositoriesTab.name, () => {
|
||||
},
|
||||
filterSortState: {
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
sortKey: SortKey.NumberOfResults,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[
|
||||
"v2.12.6",
|
||||
"v2.13.0",
|
||||
"v2.12.7",
|
||||
"v2.11.6",
|
||||
"v2.7.6",
|
||||
"v2.8.5",
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
DatabaseContents,
|
||||
DatabaseItemImpl,
|
||||
FullDatabaseOptions,
|
||||
} from "../../../src/local-databases";
|
||||
} from "../../../src/databases/local-databases";
|
||||
import { DirResult } from "tmp";
|
||||
|
||||
export function mockDbOptions(): FullDatabaseOptions {
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
defaultFilterSortState,
|
||||
filterAndSortRepositoriesWithResults,
|
||||
filterAndSortRepositoriesWithResultsByName,
|
||||
FilterKey,
|
||||
matchesFilter,
|
||||
SortKey,
|
||||
} from "../../src/pure/variant-analysis-filter-sort";
|
||||
@@ -13,32 +14,93 @@ describe(matchesFilter.name, () => {
|
||||
fullName: "github/codeql",
|
||||
};
|
||||
|
||||
const testCases = [
|
||||
{ searchValue: "", matches: true },
|
||||
{ searchValue: "github/codeql", matches: true },
|
||||
{ searchValue: "github", matches: true },
|
||||
{ searchValue: "git", matches: true },
|
||||
{ searchValue: "codeql", matches: true },
|
||||
{ searchValue: "code", matches: true },
|
||||
{ searchValue: "ql", matches: true },
|
||||
{ searchValue: "/", matches: true },
|
||||
{ searchValue: "gothub/codeql", matches: false },
|
||||
{ searchValue: "hello", matches: false },
|
||||
{ searchValue: "cod*ql", matches: false },
|
||||
{ searchValue: "cod?ql", matches: false },
|
||||
];
|
||||
describe("searchValue", () => {
|
||||
const testCases = [
|
||||
{ searchValue: "", matches: true },
|
||||
{ searchValue: "github/codeql", matches: true },
|
||||
{ searchValue: "github", matches: true },
|
||||
{ searchValue: "git", matches: true },
|
||||
{ searchValue: "codeql", matches: true },
|
||||
{ searchValue: "code", matches: true },
|
||||
{ searchValue: "ql", matches: true },
|
||||
{ searchValue: "/", matches: true },
|
||||
{ searchValue: "gothub/codeql", matches: false },
|
||||
{ searchValue: "hello", matches: false },
|
||||
{ searchValue: "cod*ql", matches: false },
|
||||
{ searchValue: "cod?ql", matches: false },
|
||||
];
|
||||
|
||||
test.each(testCases)(
|
||||
"returns $matches if searching for $searchValue",
|
||||
({ searchValue, matches }) => {
|
||||
test.each(testCases)(
|
||||
"returns $matches if searching for $searchValue",
|
||||
({ searchValue, matches }) => {
|
||||
expect(
|
||||
matchesFilter(
|
||||
{ repository },
|
||||
{
|
||||
...defaultFilterSortState,
|
||||
searchValue,
|
||||
},
|
||||
),
|
||||
).toBe(matches);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("filterKey", () => {
|
||||
it("returns true if filterKey is all and resultCount is positive", () => {
|
||||
expect(
|
||||
matchesFilter(repository, {
|
||||
...defaultFilterSortState,
|
||||
searchValue,
|
||||
}),
|
||||
).toBe(matches);
|
||||
},
|
||||
);
|
||||
matchesFilter(
|
||||
{ repository, resultCount: 1 },
|
||||
{ ...defaultFilterSortState, filterKey: FilterKey.All },
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true if filterKey is all and resultCount is zero", () => {
|
||||
expect(
|
||||
matchesFilter(
|
||||
{ repository, resultCount: 0 },
|
||||
{ ...defaultFilterSortState, filterKey: FilterKey.All },
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true if filterKey is all and resultCount is undefined", () => {
|
||||
expect(
|
||||
matchesFilter(
|
||||
{ repository },
|
||||
{ ...defaultFilterSortState, filterKey: FilterKey.All },
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true if filterKey is withResults and resultCount is positive", () => {
|
||||
expect(
|
||||
matchesFilter(
|
||||
{ repository, resultCount: 1 },
|
||||
{ ...defaultFilterSortState, filterKey: FilterKey.WithResults },
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false if filterKey is withResults and resultCount is zero", () => {
|
||||
expect(
|
||||
matchesFilter(
|
||||
{ repository, resultCount: 0 },
|
||||
{ ...defaultFilterSortState, filterKey: FilterKey.WithResults },
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false if filterKey is withResults and resultCount is undefined", () => {
|
||||
expect(
|
||||
matchesFilter(
|
||||
{ repository },
|
||||
{ ...defaultFilterSortState, filterKey: FilterKey.WithResults },
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(compareRepository.name, () => {
|
||||
@@ -65,10 +127,10 @@ describe(compareRepository.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key is name", () => {
|
||||
describe("when sort key is 'Alphabetically'", () => {
|
||||
const sorter = compareRepository({
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.Name,
|
||||
sortKey: SortKey.Alphabetically,
|
||||
});
|
||||
|
||||
const left = {
|
||||
@@ -91,10 +153,10 @@ describe(compareRepository.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key is stars", () => {
|
||||
describe("when sort key is 'Popularity'", () => {
|
||||
const sorter = compareRepository({
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.Stars,
|
||||
sortKey: SortKey.Popularity,
|
||||
});
|
||||
|
||||
const left = {
|
||||
@@ -137,10 +199,10 @@ describe(compareRepository.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key is last updated", () => {
|
||||
describe("when sort key is 'Most recent commit'", () => {
|
||||
const sorter = compareRepository({
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.LastUpdated,
|
||||
sortKey: SortKey.MostRecentCommit,
|
||||
});
|
||||
|
||||
const left = {
|
||||
@@ -209,10 +271,10 @@ describe(compareWithResults.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key is stars", () => {
|
||||
describe("when sort key is 'Popularity'", () => {
|
||||
const sorter = compareWithResults({
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.Stars,
|
||||
sortKey: SortKey.Popularity,
|
||||
});
|
||||
|
||||
const left = {
|
||||
@@ -235,10 +297,10 @@ describe(compareWithResults.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key is last updated", () => {
|
||||
describe("when sort key is 'Most recent commit'", () => {
|
||||
const sorter = compareWithResults({
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.LastUpdated,
|
||||
sortKey: SortKey.MostRecentCommit,
|
||||
});
|
||||
|
||||
const left = {
|
||||
@@ -264,7 +326,7 @@ describe(compareWithResults.name, () => {
|
||||
describe("when sort key is results count", () => {
|
||||
const sorter = compareWithResults({
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
sortKey: SortKey.NumberOfResults,
|
||||
});
|
||||
|
||||
const left = {
|
||||
@@ -349,12 +411,12 @@ describe(filterAndSortRepositoriesWithResultsByName.name, () => {
|
||||
},
|
||||
];
|
||||
|
||||
describe("when sort key is given without filter", () => {
|
||||
describe("when sort key is given without search or filter", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResultsByName(repositories, {
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
sortKey: SortKey.NumberOfResults,
|
||||
}),
|
||||
).toEqual([
|
||||
repositories[3],
|
||||
@@ -365,17 +427,41 @@ describe(filterAndSortRepositoriesWithResultsByName.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key and search filter are given", () => {
|
||||
describe("when sort key and search are given without filter", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResultsByName(repositories, {
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
sortKey: SortKey.NumberOfResults,
|
||||
searchValue: "la",
|
||||
}),
|
||||
).toEqual([repositories[2], repositories[0]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key and filter withResults are given without search", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResultsByName(repositories, {
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.NumberOfResults,
|
||||
filterKey: FilterKey.WithResults,
|
||||
}),
|
||||
).toEqual([repositories[3], repositories[2], repositories[0]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key, search, and filter withResults are given", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResultsByName(repositories, {
|
||||
sortKey: SortKey.NumberOfResults,
|
||||
filterKey: FilterKey.WithResults,
|
||||
searchValue: "r",
|
||||
}),
|
||||
).toEqual([repositories[3]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(filterAndSortRepositoriesWithResults.name, () => {
|
||||
@@ -410,12 +496,12 @@ describe(filterAndSortRepositoriesWithResults.name, () => {
|
||||
},
|
||||
];
|
||||
|
||||
describe("when sort key is given without filter", () => {
|
||||
describe("when sort key is given", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResults(repositories, {
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
sortKey: SortKey.NumberOfResults,
|
||||
}),
|
||||
).toEqual([
|
||||
repositories[3],
|
||||
@@ -426,24 +512,61 @@ describe(filterAndSortRepositoriesWithResults.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key and search filter are given", () => {
|
||||
describe("when sort key and search are given", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResults(repositories, {
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
sortKey: SortKey.NumberOfResults,
|
||||
searchValue: "la",
|
||||
}),
|
||||
).toEqual([repositories[2], repositories[0]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key, search filter, and repository ids are given", () => {
|
||||
describe("when sort key and filter withResults are given", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResults(repositories, {
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
sortKey: SortKey.NumberOfResults,
|
||||
filterKey: FilterKey.WithResults,
|
||||
}),
|
||||
).toEqual([repositories[3], repositories[2], repositories[0]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key and filter withResults are given", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResults(repositories, {
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.NumberOfResults,
|
||||
filterKey: FilterKey.WithResults,
|
||||
}),
|
||||
).toEqual([repositories[3], repositories[2], repositories[0]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key, search, and filter withResults are given", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResults(repositories, {
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.NumberOfResults,
|
||||
filterKey: FilterKey.WithResults,
|
||||
searchValue: "r",
|
||||
}),
|
||||
).toEqual([repositories[3]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key, search, filter withResults, and repository ids are given", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResults(repositories, {
|
||||
sortKey: SortKey.NumberOfResults,
|
||||
filterKey: FilterKey.WithResults,
|
||||
searchValue: "la",
|
||||
repositoryIds: [
|
||||
repositories[1].repository.id,
|
||||
|
||||
@@ -750,7 +750,7 @@ describe("Variant Analysis Manager", () => {
|
||||
variantAnalysis.id,
|
||||
{
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
sortKey: SortKey.NumberOfResults,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { join } from "path";
|
||||
import { CancellationToken, Uri, window } from "vscode";
|
||||
|
||||
import { CodeQLCliServer } from "../../../src/cli";
|
||||
import { DatabaseManager } from "../../../src/local-databases";
|
||||
import { CodeQLCliServer } from "../../../../src/cli";
|
||||
import { DatabaseManager } from "../../../../src/databases/local-databases";
|
||||
import {
|
||||
importArchiveDatabase,
|
||||
promptImportInternetDatabase,
|
||||
} from "../../../src/databaseFetcher";
|
||||
} from "../../../../src/databases/database-fetcher";
|
||||
import {
|
||||
cleanDatabases,
|
||||
dbLoc,
|
||||
DB_URL,
|
||||
getActivatedExtension,
|
||||
storagePath,
|
||||
} from "../global.helper";
|
||||
import { createMockCommandManager } from "../../__mocks__/commandsMock";
|
||||
} from "../../global.helper";
|
||||
import { createMockCommandManager } from "../../../__mocks__/commandsMock";
|
||||
|
||||
/**
|
||||
* Run various integration tests for databases
|
||||
*/
|
||||
describe("DatabaseFetcher", () => {
|
||||
describe("database-fetcher", () => {
|
||||
let databaseManager: DatabaseManager;
|
||||
let inputBoxStub: jest.SpiedFunction<typeof window.showInputBox>;
|
||||
let cli: CodeQLCliServer;
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import * as CodeQLProtocol from "../../../../src/debugger/debug-protocol";
|
||||
import { DisposableObject } from "../../../../src/pure/disposable-object";
|
||||
import { QueryResultType } from "../../../../src/pure/legacy-messages";
|
||||
import { CoreCompletedQuery } from "../../../../src/queryRunner";
|
||||
import { CoreCompletedQuery } from "../../../../src/query-server/query-runner";
|
||||
import { QueryOutputDir } from "../../../../src/run-queries-shared";
|
||||
import {
|
||||
QLDebugArgs,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Selection, Uri, window, workspace } from "vscode";
|
||||
import { join } from "path";
|
||||
|
||||
import { DatabaseManager } from "../../../../src/local-databases";
|
||||
import { DatabaseManager } from "../../../../src/databases/local-databases";
|
||||
import {
|
||||
cleanDatabases,
|
||||
ensureTestDatabase,
|
||||
|
||||
@@ -4,11 +4,11 @@ import { dirSync } from "tmp";
|
||||
import { pathToFileURL } from "url";
|
||||
import { CancellationTokenSource } from "vscode-jsonrpc";
|
||||
import * as messages from "../../../src/pure/legacy-messages";
|
||||
import * as qsClient from "../../../src/legacy-query-server/queryserver-client";
|
||||
import * as qsClient from "../../../src/query-server/legacy/query-server-client";
|
||||
import * as cli from "../../../src/cli";
|
||||
import { CellValue } from "../../../src/pure/bqrs-cli-types";
|
||||
import { describeWithCodeQL } from "../cli";
|
||||
import { QueryServerClient } from "../../../src/legacy-query-server/queryserver-client";
|
||||
import { QueryServerClient } from "../../../src/query-server/legacy/query-server-client";
|
||||
import { extLogger, ProgressReporter } from "../../../src/common";
|
||||
import { createMockApp } from "../../__mocks__/appMock";
|
||||
import { getActivatedExtension } from "../global.helper";
|
||||
|
||||
@@ -2,11 +2,11 @@ import { join, basename } from "path";
|
||||
import { dirSync } from "tmp";
|
||||
import { CancellationTokenSource } from "vscode-jsonrpc";
|
||||
import * as messages from "../../../src/pure/new-messages";
|
||||
import * as qsClient from "../../../src/query-server/queryserver-client";
|
||||
import * as qsClient from "../../../src/query-server/query-server-client";
|
||||
import * as cli from "../../../src/cli";
|
||||
import { CellValue } from "../../../src/pure/bqrs-cli-types";
|
||||
import { describeWithCodeQL } from "../cli";
|
||||
import { QueryServerClient } from "../../../src/query-server/queryserver-client";
|
||||
import { QueryServerClient } from "../../../src/query-server/query-server-client";
|
||||
import { extLogger, ProgressReporter } from "../../../src/common";
|
||||
import { QueryResultType } from "../../../src/pure/new-messages";
|
||||
import { ensureTestDatabase, getActivatedExtension } from "../global.helper";
|
||||
|
||||
@@ -9,7 +9,10 @@ import {
|
||||
} from "fs-extra";
|
||||
import { load, dump } from "js-yaml";
|
||||
|
||||
import { DatabaseItem, DatabaseManager } from "../../../src/local-databases";
|
||||
import {
|
||||
DatabaseItem,
|
||||
DatabaseManager,
|
||||
} from "../../../src/databases/local-databases";
|
||||
import {
|
||||
cleanDatabases,
|
||||
ensureTestDatabase,
|
||||
@@ -17,8 +20,11 @@ import {
|
||||
} from "../global.helper";
|
||||
import { CliVersionConstraint, CodeQLCliServer } from "../../../src/cli";
|
||||
import { describeWithCodeQL } from "../cli";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../../../src/queryRunner";
|
||||
import { SELECT_QUERY_NAME } from "../../../src/contextual/locationFinder";
|
||||
import {
|
||||
CoreCompletedQuery,
|
||||
QueryRunner,
|
||||
} from "../../../src/query-server/query-runner";
|
||||
import { SELECT_QUERY_NAME } from "../../../src/language-support";
|
||||
import { LocalQueries } from "../../../src/local-queries";
|
||||
import { QueryResultType } from "../../../src/pure/new-messages";
|
||||
import { createVSCodeCommandManager } from "../../../src/common/vscode/commands";
|
||||
|
||||
@@ -9,8 +9,7 @@ import {
|
||||
getQlPackForDbscheme,
|
||||
languageToDbScheme,
|
||||
} from "../../../src/helpers";
|
||||
import { resolveQueries } from "../../../src/contextual/queryResolver";
|
||||
import { KeyType } from "../../../src/contextual/keyType";
|
||||
import { KeyType, resolveQueries } from "../../../src/language-support";
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { getActivatedExtension } from "../global.helper";
|
||||
|
||||
|
||||
@@ -17,10 +17,11 @@ import {
|
||||
DatabaseItem,
|
||||
DatabaseManager,
|
||||
FullDatabaseOptions,
|
||||
} from "../../../src/local-databases";
|
||||
import * as databaseFetcher from "../../../src/databaseFetcher";
|
||||
} from "../../../src/databases/local-databases";
|
||||
import * as databaseFetcher from "../../../src/databases/database-fetcher";
|
||||
import { createMockDB } from "../../factories/databases/databases";
|
||||
import { asError } from "../../../src/pure/helpers-pure";
|
||||
import { Setting } from "../../../src/config";
|
||||
|
||||
describe("SkeletonQueryWizard", () => {
|
||||
let mockCli: CodeQLCliServer;
|
||||
@@ -29,6 +30,7 @@ describe("SkeletonQueryWizard", () => {
|
||||
let dir: tmp.DirResult;
|
||||
let storagePath: string;
|
||||
let quickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
|
||||
let showInputBoxSpy: jest.SpiedFunction<typeof window.showInputBox>;
|
||||
let generateSpy: jest.SpiedFunction<
|
||||
typeof QlPackGenerator.prototype.generate
|
||||
>;
|
||||
@@ -93,6 +95,9 @@ describe("SkeletonQueryWizard", () => {
|
||||
quickPickSpy = jest
|
||||
.spyOn(window, "showQuickPick")
|
||||
.mockResolvedValueOnce(mockedQuickPickItem(chosenLanguage));
|
||||
showInputBoxSpy = jest
|
||||
.spyOn(window, "showInputBox")
|
||||
.mockResolvedValue(storagePath);
|
||||
generateSpy = jest
|
||||
.spyOn(QlPackGenerator.prototype, "generate")
|
||||
.mockResolvedValue(undefined);
|
||||
@@ -315,7 +320,7 @@ describe("SkeletonQueryWizard", () => {
|
||||
|
||||
jest.spyOn(mockDbItem, "name", "get").mockReturnValue("mock-name");
|
||||
|
||||
const databaseItem = await wizard.findDatabaseItemByNwo(
|
||||
const databaseItem = await SkeletonQueryWizard.findDatabaseItemByNwo(
|
||||
mockDbItem.language,
|
||||
mockDbItem.name,
|
||||
[mockDbItem, mockDbItem2],
|
||||
@@ -325,37 +330,6 @@ describe("SkeletonQueryWizard", () => {
|
||||
JSON.stringify(mockDbItem),
|
||||
);
|
||||
});
|
||||
|
||||
it("should ignore databases with errors", async () => {
|
||||
const mockDbItem = createMockDB(dir, {
|
||||
language: "ruby",
|
||||
dateAdded: 123,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem2 = createMockDB(dir, {
|
||||
language: "javascript",
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem3 = createMockDB(dir, {
|
||||
language: "ruby",
|
||||
dateAdded: 345,
|
||||
} as FullDatabaseOptions);
|
||||
|
||||
jest.spyOn(mockDbItem, "name", "get").mockReturnValue("mock-name");
|
||||
jest.spyOn(mockDbItem3, "name", "get").mockReturnValue(mockDbItem.name);
|
||||
|
||||
jest
|
||||
.spyOn(mockDbItem, "error", "get")
|
||||
.mockReturnValue(asError("database go boom!"));
|
||||
|
||||
const databaseItem = await wizard.findDatabaseItemByNwo(
|
||||
mockDbItem.language,
|
||||
mockDbItem.name,
|
||||
[mockDbItem, mockDbItem2, mockDbItem3],
|
||||
);
|
||||
|
||||
expect(JSON.stringify(databaseItem)).toEqual(
|
||||
JSON.stringify(mockDbItem3),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the item doesn't exist", () => {
|
||||
@@ -363,7 +337,7 @@ describe("SkeletonQueryWizard", () => {
|
||||
const mockDbItem = createMockDB(dir);
|
||||
const mockDbItem2 = createMockDB(dir);
|
||||
|
||||
const databaseItem = await wizard.findDatabaseItemByNwo(
|
||||
const databaseItem = await SkeletonQueryWizard.findDatabaseItemByNwo(
|
||||
"ruby",
|
||||
"mock-nwo",
|
||||
[mockDbItem, mockDbItem2],
|
||||
@@ -384,39 +358,14 @@ describe("SkeletonQueryWizard", () => {
|
||||
language: "javascript",
|
||||
} as FullDatabaseOptions);
|
||||
|
||||
const databaseItem = await wizard.findDatabaseItemByLanguage("ruby", [
|
||||
mockDbItem,
|
||||
mockDbItem2,
|
||||
]);
|
||||
const databaseItem =
|
||||
await SkeletonQueryWizard.findDatabaseItemByLanguage("ruby", [
|
||||
mockDbItem,
|
||||
mockDbItem2,
|
||||
]);
|
||||
|
||||
expect(databaseItem).toEqual(mockDbItem);
|
||||
});
|
||||
|
||||
it("should ignore databases with errors", async () => {
|
||||
const mockDbItem = createMockDB(dir, {
|
||||
language: "ruby",
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem2 = createMockDB(dir, {
|
||||
language: "javascript",
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem3 = createMockDB(dir, {
|
||||
language: "ruby",
|
||||
} as FullDatabaseOptions);
|
||||
|
||||
jest
|
||||
.spyOn(mockDbItem, "error", "get")
|
||||
.mockReturnValue(asError("database go boom!"));
|
||||
|
||||
const databaseItem = await wizard.findDatabaseItemByLanguage("ruby", [
|
||||
mockDbItem,
|
||||
mockDbItem2,
|
||||
mockDbItem3,
|
||||
]);
|
||||
|
||||
expect(JSON.stringify(databaseItem)).toEqual(
|
||||
JSON.stringify(mockDbItem3),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the item doesn't exist", () => {
|
||||
@@ -424,13 +373,258 @@ describe("SkeletonQueryWizard", () => {
|
||||
const mockDbItem = createMockDB(dir);
|
||||
const mockDbItem2 = createMockDB(dir);
|
||||
|
||||
const databaseItem = await wizard.findDatabaseItemByLanguage("ruby", [
|
||||
mockDbItem,
|
||||
mockDbItem2,
|
||||
]);
|
||||
const databaseItem =
|
||||
await SkeletonQueryWizard.findDatabaseItemByLanguage("ruby", [
|
||||
mockDbItem,
|
||||
mockDbItem2,
|
||||
]);
|
||||
|
||||
expect(databaseItem).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("determineStoragePath", () => {
|
||||
it("should prompt the user to provide a storage path", async () => {
|
||||
const chosenPath = await wizard.determineStoragePath();
|
||||
|
||||
expect(showInputBoxSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ value: storagePath }),
|
||||
);
|
||||
expect(chosenPath).toEqual(storagePath);
|
||||
});
|
||||
|
||||
it("should write the chosen folder to settings", async () => {
|
||||
const updateValueSpy = jest.spyOn(Setting.prototype, "updateValue");
|
||||
|
||||
await wizard.determineStoragePath();
|
||||
|
||||
expect(updateValueSpy).toHaveBeenCalledWith(storagePath, 1);
|
||||
});
|
||||
|
||||
describe("when the user is using the codespace template", () => {
|
||||
let originalValue: any;
|
||||
let storedPath: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
storedPath = join(dir.name, "pickles-folder");
|
||||
ensureDirSync(storedPath);
|
||||
|
||||
originalValue = workspace
|
||||
.getConfiguration("codeQL.createQuery")
|
||||
.get("folder");
|
||||
|
||||
// Set isCodespacesTemplate to true to indicate we are in the codespace template
|
||||
await workspace
|
||||
.getConfiguration("codeQL")
|
||||
.update("codespacesTemplate", true);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await workspace
|
||||
.getConfiguration("codeQL")
|
||||
.update("codespacesTemplate", originalValue);
|
||||
});
|
||||
|
||||
it("should not prompt the user", async () => {
|
||||
const chosenPath = await wizard.determineStoragePath();
|
||||
|
||||
expect(showInputBoxSpy).not.toHaveBeenCalled();
|
||||
expect(chosenPath).toEqual(storagePath);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is already a saved storage path in settings", () => {
|
||||
describe("when the saved storage path exists", () => {
|
||||
let originalValue: any;
|
||||
let storedPath: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
storedPath = join(dir.name, "pickles-folder");
|
||||
ensureDirSync(storedPath);
|
||||
|
||||
originalValue = workspace
|
||||
.getConfiguration("codeQL.createQuery")
|
||||
.get("folder");
|
||||
await workspace
|
||||
.getConfiguration("codeQL.createQuery")
|
||||
.update("folder", storedPath);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await workspace
|
||||
.getConfiguration("codeQL.createQuery")
|
||||
.update("folder", originalValue);
|
||||
});
|
||||
|
||||
it("should return it and not prompt the user", async () => {
|
||||
const chosenPath = await wizard.determineStoragePath();
|
||||
|
||||
expect(showInputBoxSpy).not.toHaveBeenCalled();
|
||||
expect(chosenPath).toEqual(storedPath);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the saved storage path does not exist", () => {
|
||||
let originalValue: any;
|
||||
let storedPath: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
storedPath = join(dir.name, "this-folder-does-not-exist");
|
||||
|
||||
originalValue = workspace
|
||||
.getConfiguration("codeQL.createQuery")
|
||||
.get("folder");
|
||||
await workspace
|
||||
.getConfiguration("codeQL.createQuery")
|
||||
.update("folder", storedPath);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await workspace
|
||||
.getConfiguration("codeQL.createQuery")
|
||||
.update("folder", originalValue);
|
||||
});
|
||||
|
||||
it("should prompt the user for to provide a new folder name", async () => {
|
||||
const chosenPath = await wizard.determineStoragePath();
|
||||
|
||||
expect(showInputBoxSpy).toHaveBeenCalled();
|
||||
expect(chosenPath).toEqual(storagePath);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("sortDatabaseItemsByDateAdded", () => {
|
||||
describe("should return a sorted list", () => {
|
||||
it("should sort the items by dateAdded", async () => {
|
||||
const mockDbItem = createMockDB(dir, {
|
||||
dateAdded: 678,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem2 = createMockDB(dir, {
|
||||
dateAdded: 123,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem3 = createMockDB(dir, {
|
||||
dateAdded: undefined,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem4 = createMockDB(dir, {
|
||||
dateAdded: 345,
|
||||
} as FullDatabaseOptions);
|
||||
|
||||
const sortedList =
|
||||
await SkeletonQueryWizard.sortDatabaseItemsByDateAdded([
|
||||
mockDbItem,
|
||||
mockDbItem2,
|
||||
mockDbItem3,
|
||||
mockDbItem4,
|
||||
]);
|
||||
|
||||
expect(sortedList).toEqual([
|
||||
mockDbItem3,
|
||||
mockDbItem2,
|
||||
mockDbItem4,
|
||||
mockDbItem,
|
||||
]);
|
||||
});
|
||||
|
||||
it("should ignore databases with errors", async () => {
|
||||
const mockDbItem = createMockDB(dir, {
|
||||
dateAdded: 678,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem2 = createMockDB(dir, {
|
||||
dateAdded: undefined,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem3 = createMockDB(dir, {
|
||||
dateAdded: 345,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem4 = createMockDB(dir, {
|
||||
dateAdded: 123,
|
||||
} as FullDatabaseOptions);
|
||||
|
||||
jest
|
||||
.spyOn(mockDbItem, "error", "get")
|
||||
.mockReturnValue(asError("database go boom!"));
|
||||
|
||||
const sortedList =
|
||||
await SkeletonQueryWizard.sortDatabaseItemsByDateAdded([
|
||||
mockDbItem,
|
||||
mockDbItem2,
|
||||
mockDbItem3,
|
||||
mockDbItem4,
|
||||
]);
|
||||
|
||||
expect(sortedList).toEqual([mockDbItem2, mockDbItem4, mockDbItem3]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("findExistingDatabaseItem", () => {
|
||||
describe("when there are multiple items with the same name", () => {
|
||||
it("should choose the latest one", async () => {
|
||||
const mockDbItem = createMockDB(dir, {
|
||||
language: "javascript",
|
||||
dateAdded: 456,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem2 = createMockDB(dir, {
|
||||
language: "ruby",
|
||||
dateAdded: 789,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem3 = createMockDB(dir, {
|
||||
language: "javascript",
|
||||
dateAdded: 123,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem4 = createMockDB(dir, {
|
||||
language: "javascript",
|
||||
dateAdded: undefined,
|
||||
} as FullDatabaseOptions);
|
||||
|
||||
jest
|
||||
.spyOn(mockDbItem, "name", "get")
|
||||
.mockReturnValue(QUERY_LANGUAGE_TO_DATABASE_REPO["javascript"]);
|
||||
jest
|
||||
.spyOn(mockDbItem2, "name", "get")
|
||||
.mockReturnValue(QUERY_LANGUAGE_TO_DATABASE_REPO["javascript"]);
|
||||
|
||||
const databaseItem = await SkeletonQueryWizard.findExistingDatabaseItem(
|
||||
"javascript",
|
||||
[mockDbItem, mockDbItem2, mockDbItem3, mockDbItem4],
|
||||
);
|
||||
|
||||
expect(JSON.stringify(databaseItem)).toEqual(
|
||||
JSON.stringify(mockDbItem),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there are multiple items with the same language", () => {
|
||||
it("should choose the latest one", async () => {
|
||||
const mockDbItem = createMockDB(dir, {
|
||||
language: "ruby",
|
||||
dateAdded: 789,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem2 = createMockDB(dir, {
|
||||
language: "javascript",
|
||||
dateAdded: 456,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem3 = createMockDB(dir, {
|
||||
language: "ruby",
|
||||
dateAdded: 123,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem4 = createMockDB(dir, {
|
||||
language: "javascript",
|
||||
dateAdded: undefined,
|
||||
} as FullDatabaseOptions);
|
||||
|
||||
const databaseItem = await SkeletonQueryWizard.findExistingDatabaseItem(
|
||||
"javascript",
|
||||
[mockDbItem, mockDbItem2, mockDbItem3, mockDbItem4],
|
||||
);
|
||||
|
||||
expect(JSON.stringify(databaseItem)).toEqual(
|
||||
JSON.stringify(mockDbItem2),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,12 +7,15 @@ import {
|
||||
Uri,
|
||||
extensions,
|
||||
} from "vscode";
|
||||
import { DatabaseItem, DatabaseManager } from "../../src/local-databases";
|
||||
import {
|
||||
DatabaseItem,
|
||||
DatabaseManager,
|
||||
} from "../../src/databases/local-databases";
|
||||
import { CodeQLCliServer } from "../../src/cli";
|
||||
import { removeWorkspaceRefs } from "../../src/variant-analysis/run-remote-query";
|
||||
import { CodeQLExtensionInterface } from "../../src/extension";
|
||||
import { ProgressCallback } from "../../src/progress";
|
||||
import { importArchiveDatabase } from "../../src/databaseFetcher";
|
||||
import { importArchiveDatabase } from "../../src/databases/database-fetcher";
|
||||
import { createMockCommandManager } from "../__mocks__/commandsMock";
|
||||
|
||||
// This file contains helpers shared between tests that work with an activated extension.
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
DatabaseResolver,
|
||||
findSourceArchive,
|
||||
FullDatabaseOptions,
|
||||
} from "../../../src/local-databases";
|
||||
} from "../../../src/databases/local-databases";
|
||||
import { Logger } from "../../../src/common";
|
||||
import { ProgressCallback } from "../../../src/progress";
|
||||
import { CodeQLCliServer, DbInfo } from "../../../src/cli";
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
encodeSourceArchiveUri,
|
||||
} from "../../../src/archive-filesystem-provider";
|
||||
import { testDisposeHandler } from "../test-dispose-handler";
|
||||
import { QueryRunner } from "../../../src/queryRunner";
|
||||
import { QueryRunner } from "../../../src/query-server/query-runner";
|
||||
import * as helpers from "../../../src/helpers";
|
||||
import { Setting } from "../../../src/config";
|
||||
import { QlPackGenerator } from "../../../src/qlpack-generator";
|
||||
@@ -708,6 +708,7 @@ describe("local databases", () => {
|
||||
describe("openDatabase", () => {
|
||||
let createSkeletonPacksSpy: jest.SpyInstance;
|
||||
let resolveDatabaseContentsSpy: jest.SpyInstance;
|
||||
let setCurrentDatabaseItemSpy: jest.SpyInstance;
|
||||
let addDatabaseSourceArchiveFolderSpy: jest.SpyInstance;
|
||||
let mockDbItem: DatabaseItemImpl;
|
||||
|
||||
@@ -722,6 +723,11 @@ describe("local databases", () => {
|
||||
.spyOn(DatabaseResolver, "resolveDatabaseContents")
|
||||
.mockResolvedValue({} as DatabaseContentsWithDbScheme);
|
||||
|
||||
setCurrentDatabaseItemSpy = jest.spyOn(
|
||||
databaseManager,
|
||||
"setCurrentDatabaseItem",
|
||||
);
|
||||
|
||||
addDatabaseSourceArchiveFolderSpy = jest.spyOn(
|
||||
databaseManager,
|
||||
"addDatabaseSourceArchiveFolder",
|
||||
@@ -746,6 +752,19 @@ describe("local databases", () => {
|
||||
expect(resolveDatabaseContentsSpy).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should set the database as the currently selected one", async () => {
|
||||
const makeSelected = true;
|
||||
|
||||
await databaseManager.openDatabase(
|
||||
{} as ProgressCallback,
|
||||
{} as CancellationToken,
|
||||
mockDbItem.databaseUri,
|
||||
makeSelected,
|
||||
);
|
||||
|
||||
expect(setCurrentDatabaseItemSpy).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should add database source archive folder", async () => {
|
||||
await databaseManager.openDatabase(
|
||||
{} as ProgressCallback,
|
||||
@@ -762,12 +781,15 @@ describe("local databases", () => {
|
||||
jest.spyOn(Setting.prototype, "getValue").mockReturnValue(true);
|
||||
|
||||
const isTutorialDatabase = true;
|
||||
const makeSelected = true;
|
||||
const nameOverride = "CodeQL Tutorial Database";
|
||||
|
||||
await databaseManager.openDatabase(
|
||||
{} as ProgressCallback,
|
||||
{} as CancellationToken,
|
||||
mockDbItem.databaseUri,
|
||||
"CodeQL Tutorial Database",
|
||||
makeSelected,
|
||||
nameOverride,
|
||||
isTutorialDatabase,
|
||||
);
|
||||
|
||||
|
||||
@@ -3,28 +3,21 @@ import { dump as dumpYaml, load as loadYaml } from "js-yaml";
|
||||
import { outputFile, readFile } from "fs-extra";
|
||||
import { join } from "path";
|
||||
import { dir } from "tmp-promise";
|
||||
|
||||
import { pickExtensionPackModelFile } from "../../../../src/data-extensions-editor/extension-pack-picker";
|
||||
import { QlpacksInfo, ResolveExtensionsResult } from "../../../../src/cli";
|
||||
import * as helpers from "../../../../src/helpers";
|
||||
|
||||
import { pickExtensionPackModelFile } from "../../../../src/data-extensions-editor/extension-pack-picker";
|
||||
import { ExtensionPack } from "../../../../src/data-extensions-editor/shared/extension-pack";
|
||||
|
||||
describe("pickExtensionPackModelFile", () => {
|
||||
const qlPacks = {
|
||||
"my-extension-pack": ["/a/b/c/my-extension-pack"],
|
||||
"another-extension-pack": ["/a/b/c/another-extension-pack"],
|
||||
};
|
||||
const extensions = {
|
||||
models: [],
|
||||
data: {
|
||||
"/a/b/c/my-extension-pack": [
|
||||
{
|
||||
file: "/a/b/c/my-extension-pack/models/model.yml",
|
||||
index: 0,
|
||||
predicate: "sinkModel",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
let tmpDir: string;
|
||||
let extensionPackPath: string;
|
||||
let anotherExtensionPackPath: string;
|
||||
let extensionPack: ExtensionPack;
|
||||
let anotherExtensionPack: ExtensionPack;
|
||||
|
||||
let qlPacks: QlpacksInfo;
|
||||
let extensions: ResolveExtensionsResult;
|
||||
const databaseItem = {
|
||||
name: "github/vscode-codeql",
|
||||
language: "java",
|
||||
@@ -40,7 +33,42 @@ describe("pickExtensionPackModelFile", () => {
|
||||
typeof helpers.showAndLogErrorMessage
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
tmpDir = (
|
||||
await dir({
|
||||
unsafeCleanup: true,
|
||||
})
|
||||
).path;
|
||||
|
||||
extensionPackPath = join(tmpDir, "my-extension-pack");
|
||||
anotherExtensionPackPath = join(tmpDir, "another-extension-pack");
|
||||
|
||||
qlPacks = {
|
||||
"my-extension-pack": [extensionPackPath],
|
||||
"another-extension-pack": [anotherExtensionPackPath],
|
||||
};
|
||||
extensions = {
|
||||
models: [],
|
||||
data: {
|
||||
[extensionPackPath]: [
|
||||
{
|
||||
file: join(extensionPackPath, "models", "model.yml"),
|
||||
index: 0,
|
||||
predicate: "sinkModel",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
extensionPack = await createMockExtensionPack(
|
||||
extensionPackPath,
|
||||
"my-extension-pack",
|
||||
);
|
||||
anotherExtensionPack = await createMockExtensionPack(
|
||||
anotherExtensionPackPath,
|
||||
"another-extension-pack",
|
||||
);
|
||||
|
||||
showQuickPickSpy = jest
|
||||
.spyOn(window, "showQuickPick")
|
||||
.mockRejectedValue(new Error("Unexpected call to showQuickPick"));
|
||||
@@ -55,15 +83,17 @@ describe("pickExtensionPackModelFile", () => {
|
||||
});
|
||||
|
||||
it("allows choosing an existing extension pack and model file", async () => {
|
||||
const modelPath = join(extensionPackPath, "models", "model.yml");
|
||||
|
||||
const cliServer = mockCliServer(qlPacks, extensions);
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "my-extension-pack",
|
||||
extensionPack: "my-extension-pack",
|
||||
extensionPack,
|
||||
} as QuickPickItem);
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "models/model.yml",
|
||||
file: "/a/b/c/my-extension-pack/models/model.yml",
|
||||
file: modelPath,
|
||||
} as QuickPickItem);
|
||||
|
||||
expect(
|
||||
@@ -74,22 +104,23 @@ describe("pickExtensionPackModelFile", () => {
|
||||
token,
|
||||
),
|
||||
).toEqual({
|
||||
filename: "/a/b/c/my-extension-pack/models/model.yml",
|
||||
extensionPack: {
|
||||
name: "my-extension-pack",
|
||||
path: "/a/b/c/my-extension-pack",
|
||||
},
|
||||
filename: modelPath,
|
||||
extensionPack,
|
||||
});
|
||||
expect(showQuickPickSpy).toHaveBeenCalledTimes(2);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
{
|
||||
label: "my-extension-pack",
|
||||
extensionPack: "my-extension-pack",
|
||||
description: "0.0.0",
|
||||
detail: extensionPackPath,
|
||||
extensionPack,
|
||||
},
|
||||
{
|
||||
label: "another-extension-pack",
|
||||
extensionPack: "another-extension-pack",
|
||||
description: "0.0.0",
|
||||
detail: anotherExtensionPackPath,
|
||||
extensionPack: anotherExtensionPack,
|
||||
},
|
||||
{
|
||||
label: expect.stringMatching(/create/i),
|
||||
@@ -105,7 +136,7 @@ describe("pickExtensionPackModelFile", () => {
|
||||
[
|
||||
{
|
||||
label: "models/model.yml",
|
||||
file: "/a/b/c/my-extension-pack/models/model.yml",
|
||||
file: modelPath,
|
||||
},
|
||||
{
|
||||
label: expect.stringMatching(/create/i),
|
||||
@@ -121,38 +152,17 @@ describe("pickExtensionPackModelFile", () => {
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledWith([], true);
|
||||
expect(cliServer.resolveExtensions).toHaveBeenCalledTimes(1);
|
||||
expect(cliServer.resolveExtensions).toHaveBeenCalledWith(
|
||||
"/a/b/c/my-extension-pack",
|
||||
extensionPackPath,
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
it("allows choosing an existing extension pack and creating a new model file", async () => {
|
||||
const tmpDir = await dir({
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
|
||||
const cliServer = mockCliServer(
|
||||
{
|
||||
...qlPacks,
|
||||
"my-extension-pack": [tmpDir.path],
|
||||
},
|
||||
{
|
||||
models: extensions.models,
|
||||
data: {
|
||||
[tmpDir.path]: [
|
||||
{
|
||||
file: join(tmpDir.path, "models/model.yml"),
|
||||
index: 0,
|
||||
predicate: "sinkModel",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
);
|
||||
const cliServer = mockCliServer(qlPacks, extensions);
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "my-extension-pack",
|
||||
extensionPack: "my-extension-pack",
|
||||
extensionPack,
|
||||
} as QuickPickItem);
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "create",
|
||||
@@ -160,19 +170,6 @@ describe("pickExtensionPackModelFile", () => {
|
||||
} as QuickPickItem);
|
||||
showInputBoxSpy.mockResolvedValue("models/my-model.yml");
|
||||
|
||||
await outputFile(
|
||||
join(tmpDir.path, "codeql-pack.yml"),
|
||||
dumpYaml({
|
||||
name: "my-extension-pack",
|
||||
version: "0.0.0",
|
||||
library: true,
|
||||
extensionTargets: {
|
||||
"codeql/java-all": "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml"],
|
||||
}),
|
||||
);
|
||||
|
||||
expect(
|
||||
await pickExtensionPackModelFile(
|
||||
cliServer,
|
||||
@@ -181,49 +178,10 @@ describe("pickExtensionPackModelFile", () => {
|
||||
token,
|
||||
),
|
||||
).toEqual({
|
||||
filename: join(tmpDir.path, "models/my-model.yml"),
|
||||
extensionPack: {
|
||||
name: "my-extension-pack",
|
||||
path: tmpDir.path,
|
||||
},
|
||||
filename: join(extensionPackPath, "models", "my-model.yml"),
|
||||
extensionPack,
|
||||
});
|
||||
expect(showQuickPickSpy).toHaveBeenCalledTimes(2);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
{
|
||||
label: "my-extension-pack",
|
||||
extensionPack: "my-extension-pack",
|
||||
},
|
||||
{
|
||||
label: "another-extension-pack",
|
||||
extensionPack: "another-extension-pack",
|
||||
},
|
||||
{
|
||||
label: expect.stringMatching(/create/i),
|
||||
extensionPack: null,
|
||||
},
|
||||
],
|
||||
{
|
||||
title: expect.any(String),
|
||||
},
|
||||
token,
|
||||
);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
{
|
||||
label: "models/model.yml",
|
||||
file: join(tmpDir.path, "models/model.yml"),
|
||||
},
|
||||
{
|
||||
label: expect.stringMatching(/create/i),
|
||||
file: null,
|
||||
},
|
||||
],
|
||||
{
|
||||
title: expect.any(String),
|
||||
},
|
||||
token,
|
||||
);
|
||||
expect(showInputBoxSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
title: expect.any(String),
|
||||
@@ -235,7 +193,10 @@ describe("pickExtensionPackModelFile", () => {
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledTimes(1);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledWith([], true);
|
||||
expect(cliServer.resolveExtensions).toHaveBeenCalledTimes(1);
|
||||
expect(cliServer.resolveExtensions).toHaveBeenCalledWith(tmpDir.path, []);
|
||||
expect(cliServer.resolveExtensions).toHaveBeenCalledWith(
|
||||
extensionPackPath,
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
it("allows cancelling the extension pack prompt", async () => {
|
||||
@@ -262,11 +223,13 @@ describe("pickExtensionPackModelFile", () => {
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
|
||||
const newPackDir = join(tmpDir.path, "new-extension-pack");
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "codeql-custom-queries-java",
|
||||
path: tmpDir.path,
|
||||
} as QuickPickItem);
|
||||
showInputBoxSpy.mockResolvedValueOnce("my-extension-pack");
|
||||
showInputBoxSpy.mockResolvedValueOnce("new-extension-pack");
|
||||
showInputBoxSpy.mockResolvedValue("models/my-model.yml");
|
||||
|
||||
expect(
|
||||
@@ -277,15 +240,16 @@ describe("pickExtensionPackModelFile", () => {
|
||||
token,
|
||||
),
|
||||
).toEqual({
|
||||
filename: join(
|
||||
tmpDir.path,
|
||||
"my-extension-pack",
|
||||
"models",
|
||||
"my-model.yml",
|
||||
),
|
||||
filename: join(newPackDir, "models", "my-model.yml"),
|
||||
extensionPack: {
|
||||
name: "my-extension-pack",
|
||||
path: join(tmpDir.path, "my-extension-pack"),
|
||||
path: newPackDir,
|
||||
yamlPath: join(newPackDir, "codeql-pack.yml"),
|
||||
name: "new-extension-pack",
|
||||
version: "0.0.0",
|
||||
extensionTargets: {
|
||||
"codeql/java-all": "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml"],
|
||||
},
|
||||
});
|
||||
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||
@@ -311,14 +275,9 @@ describe("pickExtensionPackModelFile", () => {
|
||||
expect(cliServer.resolveExtensions).toHaveBeenCalled();
|
||||
|
||||
expect(
|
||||
loadYaml(
|
||||
await readFile(
|
||||
join(tmpDir.path, "my-extension-pack", "codeql-pack.yml"),
|
||||
"utf8",
|
||||
),
|
||||
),
|
||||
loadYaml(await readFile(join(newPackDir, "codeql-pack.yml"), "utf8")),
|
||||
).toEqual({
|
||||
name: "my-extension-pack",
|
||||
name: "new-extension-pack",
|
||||
version: "0.0.0",
|
||||
library: true,
|
||||
extensionTargets: {
|
||||
@@ -335,11 +294,13 @@ describe("pickExtensionPackModelFile", () => {
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
|
||||
const newPackDir = join(tmpDir.path, "new-extension-pack");
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "codeql-custom-queries-java",
|
||||
path: tmpDir.path,
|
||||
} as QuickPickItem);
|
||||
showInputBoxSpy.mockResolvedValueOnce("my-extension-pack");
|
||||
showInputBoxSpy.mockResolvedValueOnce("new-extension-pack");
|
||||
showInputBoxSpy.mockResolvedValue("models/my-model.yml");
|
||||
|
||||
expect(
|
||||
@@ -353,15 +314,16 @@ describe("pickExtensionPackModelFile", () => {
|
||||
token,
|
||||
),
|
||||
).toEqual({
|
||||
filename: join(
|
||||
tmpDir.path,
|
||||
"my-extension-pack",
|
||||
"models",
|
||||
"my-model.yml",
|
||||
),
|
||||
filename: join(newPackDir, "models", "my-model.yml"),
|
||||
extensionPack: {
|
||||
name: "my-extension-pack",
|
||||
path: join(tmpDir.path, "my-extension-pack"),
|
||||
path: newPackDir,
|
||||
yamlPath: join(newPackDir, "codeql-pack.yml"),
|
||||
name: "new-extension-pack",
|
||||
version: "0.0.0",
|
||||
extensionTargets: {
|
||||
"codeql/csharp-all": "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml"],
|
||||
},
|
||||
});
|
||||
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||
@@ -387,14 +349,9 @@ describe("pickExtensionPackModelFile", () => {
|
||||
expect(cliServer.resolveExtensions).toHaveBeenCalled();
|
||||
|
||||
expect(
|
||||
loadYaml(
|
||||
await readFile(
|
||||
join(tmpDir.path, "my-extension-pack", "codeql-pack.yml"),
|
||||
"utf8",
|
||||
),
|
||||
),
|
||||
loadYaml(await readFile(join(newPackDir, "codeql-pack.yml"), "utf8")),
|
||||
).toEqual({
|
||||
name: "my-extension-pack",
|
||||
name: "new-extension-pack",
|
||||
version: "0.0.0",
|
||||
library: true,
|
||||
extensionTargets: {
|
||||
@@ -459,10 +416,7 @@ describe("pickExtensionPackModelFile", () => {
|
||||
{ models: [], data: {} },
|
||||
);
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "my-extension-pack",
|
||||
extensionPack: "my-extension-pack",
|
||||
} as QuickPickItem);
|
||||
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||
|
||||
expect(
|
||||
await pickExtensionPackModelFile(
|
||||
@@ -474,10 +428,22 @@ describe("pickExtensionPackModelFile", () => {
|
||||
).toEqual(undefined);
|
||||
expect(showAndLogErrorMessageSpy).toHaveBeenCalledTimes(1);
|
||||
expect(showAndLogErrorMessageSpy).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/could not be resolved to a single location/),
|
||||
expect.stringMatching(/resolves to multiple paths/),
|
||||
expect.anything(),
|
||||
);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
{
|
||||
label: expect.stringMatching(/create/i),
|
||||
extensionPack: null,
|
||||
},
|
||||
],
|
||||
{
|
||||
title: "Select extension pack to use",
|
||||
},
|
||||
token,
|
||||
);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||
expect(cliServer.resolveExtensions).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -487,7 +453,7 @@ describe("pickExtensionPackModelFile", () => {
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "my-extension-pack",
|
||||
extensionPack: "my-extension-pack",
|
||||
extensionPack,
|
||||
} as QuickPickItem);
|
||||
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||
|
||||
@@ -508,29 +474,21 @@ describe("pickExtensionPackModelFile", () => {
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
|
||||
const extensionPack = await createMockExtensionPack(
|
||||
tmpDir.path,
|
||||
"no-extension-pack",
|
||||
);
|
||||
|
||||
const cliServer = mockCliServer(
|
||||
{
|
||||
"my-extension-pack": [tmpDir.path],
|
||||
"no-extension-pack": [tmpDir.path],
|
||||
},
|
||||
{ models: [], data: {} },
|
||||
);
|
||||
|
||||
await outputFile(
|
||||
join(tmpDir.path, "codeql-pack.yml"),
|
||||
dumpYaml({
|
||||
name: "my-extension-pack",
|
||||
version: "0.0.0",
|
||||
library: true,
|
||||
extensionTargets: {
|
||||
"codeql/java-all": "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml"],
|
||||
}),
|
||||
);
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "my-extension-pack",
|
||||
extensionPack: "my-extension-pack",
|
||||
label: "no-extension-pack",
|
||||
extensionPack,
|
||||
} as QuickPickItem);
|
||||
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||
showInputBoxSpy.mockResolvedValue("models/my-model.yml");
|
||||
@@ -544,10 +502,7 @@ describe("pickExtensionPackModelFile", () => {
|
||||
),
|
||||
).toEqual({
|
||||
filename: join(tmpDir.path, "models", "my-model.yml"),
|
||||
extensionPack: {
|
||||
name: "my-extension-pack",
|
||||
path: tmpDir.path,
|
||||
},
|
||||
extensionPack,
|
||||
});
|
||||
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||
expect(showInputBoxSpy).toHaveBeenCalledWith(
|
||||
@@ -574,10 +529,6 @@ describe("pickExtensionPackModelFile", () => {
|
||||
{ models: [], data: {} },
|
||||
);
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "my-extension-pack",
|
||||
extensionPack: "my-extension-pack",
|
||||
} as QuickPickItem);
|
||||
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||
showAndLogErrorMessageSpy.mockResolvedValue(undefined);
|
||||
|
||||
@@ -590,13 +541,26 @@ describe("pickExtensionPackModelFile", () => {
|
||||
),
|
||||
).toEqual(undefined);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
{
|
||||
label: expect.stringMatching(/create/i),
|
||||
extensionPack: null,
|
||||
},
|
||||
],
|
||||
{
|
||||
title: "Select extension pack to use",
|
||||
},
|
||||
token,
|
||||
);
|
||||
expect(showInputBoxSpy).not.toHaveBeenCalled();
|
||||
expect(showAndLogErrorMessageSpy).toHaveBeenCalledTimes(1);
|
||||
expect(showAndLogErrorMessageSpy).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/codeql-pack\.yml/),
|
||||
expect.stringMatching(/my-extension-pack/),
|
||||
expect.anything(),
|
||||
);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||
expect(cliServer.resolveExtensions).toHaveBeenCalled();
|
||||
expect(cliServer.resolveExtensions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows an error when the pack YAML file is invalid", async () => {
|
||||
@@ -613,10 +577,6 @@ describe("pickExtensionPackModelFile", () => {
|
||||
|
||||
await outputFile(join(tmpDir.path, "codeql-pack.yml"), dumpYaml("java"));
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "my-extension-pack",
|
||||
extensionPack: "my-extension-pack",
|
||||
} as QuickPickItem);
|
||||
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||
showAndLogErrorMessageSpy.mockResolvedValue(undefined);
|
||||
|
||||
@@ -629,13 +589,26 @@ describe("pickExtensionPackModelFile", () => {
|
||||
),
|
||||
).toEqual(undefined);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
{
|
||||
label: expect.stringMatching(/create/i),
|
||||
extensionPack: null,
|
||||
},
|
||||
],
|
||||
{
|
||||
title: "Select extension pack to use",
|
||||
},
|
||||
token,
|
||||
);
|
||||
expect(showInputBoxSpy).not.toHaveBeenCalled();
|
||||
expect(showAndLogErrorMessageSpy).toHaveBeenCalledTimes(1);
|
||||
expect(showAndLogErrorMessageSpy).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/Could not parse/),
|
||||
expect.stringMatching(/my-extension-pack/),
|
||||
expect.anything(),
|
||||
);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||
expect(cliServer.resolveExtensions).toHaveBeenCalled();
|
||||
expect(cliServer.resolveExtensions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows an error when the pack YAML does not contain dataExtensions", async () => {
|
||||
@@ -662,10 +635,6 @@ describe("pickExtensionPackModelFile", () => {
|
||||
}),
|
||||
);
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "my-extension-pack",
|
||||
extensionPack: "my-extension-pack",
|
||||
} as QuickPickItem);
|
||||
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||
showAndLogErrorMessageSpy.mockResolvedValue(undefined);
|
||||
|
||||
@@ -678,13 +647,26 @@ describe("pickExtensionPackModelFile", () => {
|
||||
),
|
||||
).toEqual(undefined);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
{
|
||||
label: expect.stringMatching(/create/i),
|
||||
extensionPack: null,
|
||||
},
|
||||
],
|
||||
{
|
||||
title: "Select extension pack to use",
|
||||
},
|
||||
token,
|
||||
);
|
||||
expect(showInputBoxSpy).not.toHaveBeenCalled();
|
||||
expect(showAndLogErrorMessageSpy).toHaveBeenCalledTimes(1);
|
||||
expect(showAndLogErrorMessageSpy).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/Expected 'dataExtensions' to be/),
|
||||
expect.stringMatching(/my-extension-pack/),
|
||||
expect.anything(),
|
||||
);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||
expect(cliServer.resolveExtensions).toHaveBeenCalled();
|
||||
expect(cliServer.resolveExtensions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows an error when the pack YAML dataExtensions is invalid", async () => {
|
||||
@@ -714,10 +696,6 @@ describe("pickExtensionPackModelFile", () => {
|
||||
}),
|
||||
);
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "my-extension-pack",
|
||||
extensionPack: "my-extension-pack",
|
||||
} as QuickPickItem);
|
||||
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||
showAndLogErrorMessageSpy.mockResolvedValue(undefined);
|
||||
|
||||
@@ -730,13 +708,26 @@ describe("pickExtensionPackModelFile", () => {
|
||||
),
|
||||
).toEqual(undefined);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
{
|
||||
label: expect.stringMatching(/create/i),
|
||||
extensionPack: null,
|
||||
},
|
||||
],
|
||||
{
|
||||
title: "Select extension pack to use",
|
||||
},
|
||||
token,
|
||||
);
|
||||
expect(showInputBoxSpy).not.toHaveBeenCalled();
|
||||
expect(showAndLogErrorMessageSpy).toHaveBeenCalledTimes(1);
|
||||
expect(showAndLogErrorMessageSpy).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/Expected 'dataExtensions' to be/),
|
||||
expect.stringMatching(/my-extension-pack/),
|
||||
expect.anything(),
|
||||
);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||
expect(cliServer.resolveExtensions).toHaveBeenCalled();
|
||||
expect(cliServer.resolveExtensions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows cancelling the new file input box", async () => {
|
||||
@@ -744,29 +735,24 @@ describe("pickExtensionPackModelFile", () => {
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
|
||||
const newExtensionPack = await createMockExtensionPack(
|
||||
tmpDir.path,
|
||||
"new-extension-pack",
|
||||
);
|
||||
|
||||
const cliServer = mockCliServer(
|
||||
{
|
||||
"my-extension-pack": [tmpDir.path],
|
||||
},
|
||||
{ models: [], data: {} },
|
||||
);
|
||||
|
||||
await outputFile(
|
||||
join(tmpDir.path, "codeql-pack.yml"),
|
||||
dumpYaml({
|
||||
name: "my-extension-pack",
|
||||
version: "0.0.0",
|
||||
library: true,
|
||||
extensionTargets: {
|
||||
"codeql/java-all": "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml"],
|
||||
}),
|
||||
{
|
||||
models: [],
|
||||
data: {},
|
||||
},
|
||||
);
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "my-extension-pack",
|
||||
extensionPack: "my-extension-pack",
|
||||
label: "new-extension-pack",
|
||||
extensionPack: newExtensionPack,
|
||||
} as QuickPickItem);
|
||||
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||
showInputBoxSpy.mockResolvedValue(undefined);
|
||||
@@ -833,36 +819,31 @@ describe("pickExtensionPackModelFile", () => {
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
|
||||
const extensionPack = await createMockExtensionPack(
|
||||
tmpDir.path,
|
||||
"new-extension-pack",
|
||||
{
|
||||
dataExtensions: ["models/**/*.yml", "data/**/*.yml"],
|
||||
},
|
||||
);
|
||||
|
||||
const cliServer = mockCliServer(
|
||||
{
|
||||
"my-extension-pack": [tmpDir.path],
|
||||
"new-extension-pack": [extensionPack.path],
|
||||
},
|
||||
{ models: [], data: {} },
|
||||
);
|
||||
|
||||
const qlpackPath = join(tmpDir.path, "codeql-pack.yml");
|
||||
await outputFile(
|
||||
qlpackPath,
|
||||
dumpYaml({
|
||||
name: "my-extension-pack",
|
||||
version: "0.0.0",
|
||||
library: true,
|
||||
extensionTargets: {
|
||||
"codeql/java-all": "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml", "data/**/*.yml"],
|
||||
}),
|
||||
);
|
||||
await outputFile(
|
||||
join(tmpDir.path, "models", "model.yml"),
|
||||
join(extensionPack.path, "models", "model.yml"),
|
||||
dumpYaml({
|
||||
extensions: [],
|
||||
}),
|
||||
);
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "my-extension-pack",
|
||||
extensionPack: "my-extension-pack",
|
||||
label: "new-extension-pack",
|
||||
extensionPack,
|
||||
} as QuickPickItem);
|
||||
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||
showInputBoxSpy.mockResolvedValue(undefined);
|
||||
@@ -893,10 +874,10 @@ describe("pickExtensionPackModelFile", () => {
|
||||
"File must be in the extension pack",
|
||||
);
|
||||
expect(await validateFile("model.yml")).toEqual(
|
||||
`File must match one of the patterns in 'dataExtensions' in ${qlpackPath}`,
|
||||
`File must match one of the patterns in 'dataExtensions' in ${extensionPack.yamlPath}`,
|
||||
);
|
||||
expect(await validateFile("models/model.yaml")).toEqual(
|
||||
`File must match one of the patterns in 'dataExtensions' in ${qlpackPath}`,
|
||||
`File must match one of the patterns in 'dataExtensions' in ${extensionPack.yamlPath}`,
|
||||
);
|
||||
expect(await validateFile("models/my-model.yml")).toBeUndefined();
|
||||
expect(await validateFile("models/nested/model.yml")).toBeUndefined();
|
||||
@@ -910,7 +891,7 @@ describe("pickExtensionPackModelFile", () => {
|
||||
|
||||
const cliServer = mockCliServer(
|
||||
{
|
||||
"my-extension-pack": [tmpDir.path],
|
||||
"new-extension-pack": [tmpDir.path],
|
||||
},
|
||||
{ models: [], data: {} },
|
||||
);
|
||||
@@ -919,7 +900,7 @@ describe("pickExtensionPackModelFile", () => {
|
||||
await outputFile(
|
||||
qlpackPath,
|
||||
dumpYaml({
|
||||
name: "my-extension-pack",
|
||||
name: "new-extension-pack",
|
||||
version: "0.0.0",
|
||||
library: true,
|
||||
extensionTargets: {
|
||||
@@ -936,8 +917,17 @@ describe("pickExtensionPackModelFile", () => {
|
||||
);
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "my-extension-pack",
|
||||
extensionPack: "my-extension-pack",
|
||||
label: "new-extension-pack",
|
||||
extensionPack: {
|
||||
path: tmpDir.path,
|
||||
yamlPath: qlpackPath,
|
||||
name: "new-extension-pack",
|
||||
version: "0.0.0",
|
||||
extensionTargets: {
|
||||
"codeql/java-all": "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml"],
|
||||
},
|
||||
} as QuickPickItem);
|
||||
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||
showInputBoxSpy.mockResolvedValue(undefined);
|
||||
@@ -959,6 +949,63 @@ describe("pickExtensionPackModelFile", () => {
|
||||
|
||||
expect(await validateFile("models/my-model.yml")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("only shows extension packs for the database language", async () => {
|
||||
const csharpPack = await createMockExtensionPack(
|
||||
join(tmpDir, "csharp-extensions"),
|
||||
"csharp-extension-pack",
|
||||
{
|
||||
version: "0.5.3",
|
||||
extensionTargets: {
|
||||
"codeql/csharp-all": "*",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const cliServer = mockCliServer(
|
||||
{
|
||||
...qlPacks,
|
||||
"csharp-extension-pack": [csharpPack.path],
|
||||
},
|
||||
extensions,
|
||||
);
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||
|
||||
expect(
|
||||
await pickExtensionPackModelFile(
|
||||
cliServer,
|
||||
{
|
||||
...databaseItem,
|
||||
language: "csharp",
|
||||
},
|
||||
progress,
|
||||
token,
|
||||
),
|
||||
).toEqual(undefined);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
{
|
||||
label: "csharp-extension-pack",
|
||||
description: "0.5.3",
|
||||
detail: csharpPack.path,
|
||||
extensionPack: csharpPack,
|
||||
},
|
||||
{
|
||||
label: expect.stringMatching(/create/i),
|
||||
extensionPack: null,
|
||||
},
|
||||
],
|
||||
{
|
||||
title: expect.any(String),
|
||||
},
|
||||
token,
|
||||
);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledTimes(1);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledWith([], true);
|
||||
expect(cliServer.resolveExtensions).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
function mockCliServer(
|
||||
@@ -970,3 +1017,40 @@ function mockCliServer(
|
||||
resolveExtensions: jest.fn().mockResolvedValue(extensions),
|
||||
};
|
||||
}
|
||||
|
||||
async function createMockExtensionPack(
|
||||
path: string,
|
||||
name: string,
|
||||
data: Partial<ExtensionPack> = {},
|
||||
): Promise<ExtensionPack> {
|
||||
const extensionPack: ExtensionPack = {
|
||||
path,
|
||||
yamlPath: join(path, "codeql-pack.yml"),
|
||||
name,
|
||||
version: "0.0.0",
|
||||
extensionTargets: {
|
||||
"codeql/java-all": "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml"],
|
||||
...data,
|
||||
};
|
||||
|
||||
await writeExtensionPackToDisk(extensionPack);
|
||||
|
||||
return extensionPack;
|
||||
}
|
||||
|
||||
async function writeExtensionPackToDisk(
|
||||
extensionPack: ExtensionPack,
|
||||
): Promise<void> {
|
||||
await outputFile(
|
||||
extensionPack.yamlPath,
|
||||
dumpYaml({
|
||||
name: extensionPack.name,
|
||||
version: extensionPack.version,
|
||||
library: true,
|
||||
extensionTargets: extensionPack.extensionTargets,
|
||||
dataExtensions: extensionPack.dataExtensions,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
} from "../../../../src/data-extensions-editor/external-api-usage-query";
|
||||
import { createMockLogger } from "../../../__mocks__/loggerMock";
|
||||
import type { Uri } from "vscode";
|
||||
import { DatabaseKind } from "../../../../src/local-databases";
|
||||
import { DatabaseKind } from "../../../../src/databases/local-databases";
|
||||
import { file } from "tmp-promise";
|
||||
import { QueryResultType } from "../../../../src/pure/new-messages";
|
||||
import { readdir, readFile } from "fs-extra";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user