Merge branch 'main' into aeisenberg/cli-version-telemetry
This commit is contained in:
1692
extensions/ql-vscode/package-lock.json
generated
1692
extensions/ql-vscode/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1461,6 +1461,7 @@
|
|||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"immutable": "^4.0.0",
|
"immutable": "^4.0.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
|
"minimatch": "^9.0.0",
|
||||||
"minimist": "~1.2.6",
|
"minimist": "~1.2.6",
|
||||||
"msw": "^1.2.0",
|
"msw": "^1.2.0",
|
||||||
"nanoid": "^3.2.0",
|
"nanoid": "^3.2.0",
|
||||||
@@ -1531,7 +1532,7 @@
|
|||||||
"@types/through2": "^2.0.36",
|
"@types/through2": "^2.0.36",
|
||||||
"@types/tmp": "^0.1.0",
|
"@types/tmp": "^0.1.0",
|
||||||
"@types/unzipper": "~0.10.1",
|
"@types/unzipper": "~0.10.1",
|
||||||
"@types/vscode": "^1.59.0",
|
"@types/vscode": "^1.67.0",
|
||||||
"@types/webpack": "^5.28.0",
|
"@types/webpack": "^5.28.0",
|
||||||
"@types/webpack-env": "^1.18.0",
|
"@types/webpack-env": "^1.18.0",
|
||||||
"@types/xml2js": "~0.4.4",
|
"@types/xml2js": "~0.4.4",
|
||||||
@@ -1555,7 +1556,7 @@
|
|||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-storybook": "^0.6.4",
|
"eslint-plugin-storybook": "^0.6.4",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"glob": "^9.3.2",
|
"glob": "^10.0.0",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-esbuild": "^0.10.5",
|
"gulp-esbuild": "^0.10.5",
|
||||||
"gulp-replace": "^1.1.3",
|
"gulp-replace": "^1.1.3",
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { showAndLogErrorMessage } from "../helpers";
|
|||||||
import { withProgress } from "../progress";
|
import { withProgress } from "../progress";
|
||||||
import { pickExtensionPackModelFile } from "./extension-pack-picker";
|
import { pickExtensionPackModelFile } from "./extension-pack-picker";
|
||||||
|
|
||||||
|
const SUPPORTED_LANGUAGES: string[] = ["java", "csharp"];
|
||||||
|
|
||||||
export class DataExtensionsEditorModule {
|
export class DataExtensionsEditorModule {
|
||||||
private readonly queryStorageDir: string;
|
private readonly queryStorageDir: string;
|
||||||
|
|
||||||
@@ -51,15 +53,22 @@ export class DataExtensionsEditorModule {
|
|||||||
|
|
||||||
public getCommands(): DataExtensionsEditorCommands {
|
public getCommands(): DataExtensionsEditorCommands {
|
||||||
return {
|
return {
|
||||||
"codeQL.openDataExtensionsEditor": async () =>
|
"codeQL.openDataExtensionsEditor": async () => {
|
||||||
withProgress(
|
const db = this.databaseManager.currentDatabaseItem;
|
||||||
async (progress) => {
|
if (!db) {
|
||||||
const db = this.databaseManager.currentDatabaseItem;
|
void showAndLogErrorMessage("No database selected");
|
||||||
if (!db) {
|
return;
|
||||||
void showAndLogErrorMessage("No database selected");
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!SUPPORTED_LANGUAGES.includes(db.language)) {
|
||||||
|
void showAndLogErrorMessage(
|
||||||
|
`The data extensions editor is not supported for ${db.language} databases.`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return withProgress(
|
||||||
|
async (progress, token) => {
|
||||||
if (!(await this.cliServer.cliConstraints.supportsQlpacksKind())) {
|
if (!(await this.cliServer.cliConstraints.supportsQlpacksKind())) {
|
||||||
void showAndLogErrorMessage(
|
void showAndLogErrorMessage(
|
||||||
`This feature requires CodeQL CLI version ${CliVersionConstraint.CLI_VERSION_WITH_QLPACKS_KIND.format()} or later.`,
|
`This feature requires CodeQL CLI version ${CliVersionConstraint.CLI_VERSION_WITH_QLPACKS_KIND.format()} or later.`,
|
||||||
@@ -69,7 +78,9 @@ export class DataExtensionsEditorModule {
|
|||||||
|
|
||||||
const modelFile = await pickExtensionPackModelFile(
|
const modelFile = await pickExtensionPackModelFile(
|
||||||
this.cliServer,
|
this.cliServer,
|
||||||
|
db,
|
||||||
progress,
|
progress,
|
||||||
|
token,
|
||||||
);
|
);
|
||||||
if (!modelFile) {
|
if (!modelFile) {
|
||||||
return;
|
return;
|
||||||
@@ -90,7 +101,8 @@ export class DataExtensionsEditorModule {
|
|||||||
{
|
{
|
||||||
title: "Opening Data Extensions Editor",
|
title: "Opening Data Extensions Editor",
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
showAndLogExceptionWithTelemetry,
|
showAndLogExceptionWithTelemetry,
|
||||||
} from "../helpers";
|
} from "../helpers";
|
||||||
import { extLogger } from "../common";
|
import { extLogger } from "../common";
|
||||||
import { readFile, writeFile } from "fs-extra";
|
import { outputFile, readFile } from "fs-extra";
|
||||||
import { load as loadYaml } from "js-yaml";
|
import { load as loadYaml } from "js-yaml";
|
||||||
import { DatabaseItem, DatabaseManager } from "../local-databases";
|
import { DatabaseItem, DatabaseManager } from "../local-databases";
|
||||||
import { CodeQLCliServer } from "../cli";
|
import { CodeQLCliServer } from "../cli";
|
||||||
@@ -148,9 +148,13 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
|||||||
externalApiUsages: ExternalApiUsage[],
|
externalApiUsages: ExternalApiUsage[],
|
||||||
modeledMethods: Record<string, ModeledMethod>,
|
modeledMethods: Record<string, ModeledMethod>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const yaml = createDataExtensionYaml(externalApiUsages, modeledMethods);
|
const yaml = createDataExtensionYaml(
|
||||||
|
this.databaseItem.language,
|
||||||
|
externalApiUsages,
|
||||||
|
modeledMethods,
|
||||||
|
);
|
||||||
|
|
||||||
await writeFile(this.modelFilename, yaml);
|
await outputFile(this.modelFilename, yaml);
|
||||||
|
|
||||||
void extLogger.log(`Saved data extension YAML to ${this.modelFilename}`);
|
void extLogger.log(`Saved data extension YAML to ${this.modelFilename}`);
|
||||||
}
|
}
|
||||||
@@ -194,7 +198,6 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
|||||||
queryRunner: this.queryRunner,
|
queryRunner: this.queryRunner,
|
||||||
databaseItem: this.databaseItem,
|
databaseItem: this.databaseItem,
|
||||||
queryStorageDir: this.queryStorageDir,
|
queryStorageDir: this.queryStorageDir,
|
||||||
logger: extLogger,
|
|
||||||
progress: (progressUpdate: ProgressUpdate) => {
|
progress: (progressUpdate: ProgressUpdate) => {
|
||||||
void this.showProgress(progressUpdate, 1500);
|
void this.showProgress(progressUpdate, 1500);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,21 +1,49 @@
|
|||||||
import { relative, sep } from "path";
|
import { join, relative, resolve, sep } from "path";
|
||||||
import { window } from "vscode";
|
import { outputFile, pathExists, readFile } from "fs-extra";
|
||||||
|
import { dump as dumpYaml, load as loadYaml } from "js-yaml";
|
||||||
|
import { minimatch } from "minimatch";
|
||||||
|
import { CancellationToken, window } from "vscode";
|
||||||
import { CodeQLCliServer } from "../cli";
|
import { CodeQLCliServer } from "../cli";
|
||||||
import { getOnDiskWorkspaceFolders, showAndLogErrorMessage } from "../helpers";
|
import {
|
||||||
|
getOnDiskWorkspaceFolders,
|
||||||
|
getOnDiskWorkspaceFoldersObjects,
|
||||||
|
showAndLogErrorMessage,
|
||||||
|
} from "../helpers";
|
||||||
import { ProgressCallback } from "../progress";
|
import { ProgressCallback } from "../progress";
|
||||||
|
import { DatabaseItem } from "../local-databases";
|
||||||
|
import { getQlPackPath, QLPACK_FILENAMES } from "../pure/ql";
|
||||||
|
|
||||||
const maxStep = 3;
|
const maxStep = 3;
|
||||||
|
|
||||||
|
const packNamePartRegex = /[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
|
||||||
|
const packNameRegex = new RegExp(
|
||||||
|
`^(?:(?<scope>${packNamePartRegex.source})/)?(?<name>${packNamePartRegex.source})$`,
|
||||||
|
);
|
||||||
|
const packNameLength = 128;
|
||||||
|
|
||||||
export async function pickExtensionPackModelFile(
|
export async function pickExtensionPackModelFile(
|
||||||
cliServer: Pick<CodeQLCliServer, "resolveQlpacks" | "resolveExtensions">,
|
cliServer: Pick<CodeQLCliServer, "resolveQlpacks" | "resolveExtensions">,
|
||||||
|
databaseItem: Pick<DatabaseItem, "name" | "language">,
|
||||||
progress: ProgressCallback,
|
progress: ProgressCallback,
|
||||||
|
token: CancellationToken,
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
const extensionPackPath = await pickExtensionPack(cliServer, progress);
|
const extensionPackPath = await pickExtensionPack(
|
||||||
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
);
|
||||||
if (!extensionPackPath) {
|
if (!extensionPackPath) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const modelFile = await pickModelFile(cliServer, progress, extensionPackPath);
|
const modelFile = await pickModelFile(
|
||||||
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
extensionPackPath,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
);
|
||||||
if (!modelFile) {
|
if (!modelFile) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -25,7 +53,9 @@ export async function pickExtensionPackModelFile(
|
|||||||
|
|
||||||
async function pickExtensionPack(
|
async function pickExtensionPack(
|
||||||
cliServer: Pick<CodeQLCliServer, "resolveQlpacks">,
|
cliServer: Pick<CodeQLCliServer, "resolveQlpacks">,
|
||||||
|
databaseItem: Pick<DatabaseItem, "name" | "language">,
|
||||||
progress: ProgressCallback,
|
progress: ProgressCallback,
|
||||||
|
token: CancellationToken,
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
progress({
|
progress({
|
||||||
message: "Resolving extension packs...",
|
message: "Resolving extension packs...",
|
||||||
@@ -36,10 +66,20 @@ async function pickExtensionPack(
|
|||||||
// Get all existing extension packs in the workspace
|
// Get all existing extension packs in the workspace
|
||||||
const additionalPacks = getOnDiskWorkspaceFolders();
|
const additionalPacks = getOnDiskWorkspaceFolders();
|
||||||
const extensionPacks = await cliServer.resolveQlpacks(additionalPacks, true);
|
const extensionPacks = await cliServer.resolveQlpacks(additionalPacks, true);
|
||||||
const options = Object.keys(extensionPacks).map((pack) => ({
|
|
||||||
label: pack,
|
if (Object.keys(extensionPacks).length === 0) {
|
||||||
extensionPack: pack,
|
return pickNewExtensionPack(databaseItem, token);
|
||||||
}));
|
}
|
||||||
|
|
||||||
|
const options: Array<{ label: string; extensionPack: string | null }> =
|
||||||
|
Object.keys(extensionPacks).map((pack) => ({
|
||||||
|
label: pack,
|
||||||
|
extensionPack: pack,
|
||||||
|
}));
|
||||||
|
options.push({
|
||||||
|
label: "Create new extension pack",
|
||||||
|
extensionPack: null,
|
||||||
|
});
|
||||||
|
|
||||||
progress({
|
progress({
|
||||||
message: "Choosing extension pack...",
|
message: "Choosing extension pack...",
|
||||||
@@ -47,13 +87,21 @@ async function pickExtensionPack(
|
|||||||
maxStep,
|
maxStep,
|
||||||
});
|
});
|
||||||
|
|
||||||
const extensionPackOption = await window.showQuickPick(options, {
|
const extensionPackOption = await window.showQuickPick(
|
||||||
title: "Select extension pack to use",
|
options,
|
||||||
});
|
{
|
||||||
|
title: "Select extension pack to use",
|
||||||
|
},
|
||||||
|
token,
|
||||||
|
);
|
||||||
if (!extensionPackOption) {
|
if (!extensionPackOption) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!extensionPackOption.extensionPack) {
|
||||||
|
return pickNewExtensionPack(databaseItem, token);
|
||||||
|
}
|
||||||
|
|
||||||
const extensionPackPaths = extensionPacks[extensionPackOption.extensionPack];
|
const extensionPackPaths = extensionPacks[extensionPackOption.extensionPack];
|
||||||
if (extensionPackPaths.length !== 1) {
|
if (extensionPackPaths.length !== 1) {
|
||||||
void showAndLogErrorMessage(
|
void showAndLogErrorMessage(
|
||||||
@@ -74,8 +122,10 @@ async function pickExtensionPack(
|
|||||||
|
|
||||||
async function pickModelFile(
|
async function pickModelFile(
|
||||||
cliServer: Pick<CodeQLCliServer, "resolveExtensions">,
|
cliServer: Pick<CodeQLCliServer, "resolveExtensions">,
|
||||||
progress: ProgressCallback,
|
databaseItem: Pick<DatabaseItem, "name">,
|
||||||
extensionPackPath: string,
|
extensionPackPath: string,
|
||||||
|
progress: ProgressCallback,
|
||||||
|
token: CancellationToken,
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
// Find the existing model files in the extension pack
|
// Find the existing model files in the extension pack
|
||||||
const additionalPacks = getOnDiskWorkspaceFolders();
|
const additionalPacks = getOnDiskWorkspaceFolders();
|
||||||
@@ -92,13 +142,21 @@ async function pickModelFile(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileOptions: Array<{ label: string; file: string }> = [];
|
if (modelFiles.size === 0) {
|
||||||
|
return pickNewModelFile(databaseItem, extensionPackPath, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileOptions: Array<{ label: string; file: string | null }> = [];
|
||||||
for (const file of modelFiles) {
|
for (const file of modelFiles) {
|
||||||
fileOptions.push({
|
fileOptions.push({
|
||||||
label: relative(extensionPackPath, file).replaceAll(sep, "/"),
|
label: relative(extensionPackPath, file).replaceAll(sep, "/"),
|
||||||
file,
|
file,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
fileOptions.push({
|
||||||
|
label: "Create new model file",
|
||||||
|
file: null,
|
||||||
|
});
|
||||||
|
|
||||||
progress({
|
progress({
|
||||||
message: "Choosing model file...",
|
message: "Choosing model file...",
|
||||||
@@ -106,13 +164,186 @@ async function pickModelFile(
|
|||||||
maxStep,
|
maxStep,
|
||||||
});
|
});
|
||||||
|
|
||||||
const fileOption = await window.showQuickPick(fileOptions, {
|
const fileOption = await window.showQuickPick(
|
||||||
title: "Select model file to use",
|
fileOptions,
|
||||||
});
|
{
|
||||||
|
title: "Select model file to use",
|
||||||
|
},
|
||||||
|
token,
|
||||||
|
);
|
||||||
|
|
||||||
if (!fileOption) {
|
if (!fileOption) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileOption.file) {
|
||||||
|
return fileOption.file;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pickNewModelFile(databaseItem, extensionPackPath, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pickNewExtensionPack(
|
||||||
|
databaseItem: Pick<DatabaseItem, "name" | "language">,
|
||||||
|
token: CancellationToken,
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
const workspaceFolders = getOnDiskWorkspaceFoldersObjects();
|
||||||
|
const workspaceFolderOptions = workspaceFolders.map((folder) => ({
|
||||||
|
label: folder.name,
|
||||||
|
detail: folder.uri.fsPath,
|
||||||
|
path: folder.uri.fsPath,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// We're not using window.showWorkspaceFolderPick because that also includes the database source folders while
|
||||||
|
// we only want to include on-disk workspace folders.
|
||||||
|
const workspaceFolder = await window.showQuickPick(workspaceFolderOptions, {
|
||||||
|
title: "Select workspace folder to create extension pack in",
|
||||||
|
});
|
||||||
|
if (!workspaceFolder) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const packName = await window.showInputBox(
|
||||||
|
{
|
||||||
|
title: "Create new extension pack",
|
||||||
|
prompt: "Enter name of extension pack",
|
||||||
|
placeHolder: `e.g. ${databaseItem.name}-extensions`,
|
||||||
|
validateInput: async (value: string): Promise<string | undefined> => {
|
||||||
|
if (!value) {
|
||||||
|
return "Pack name must not be empty";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.length > packNameLength) {
|
||||||
|
return `Pack name must be no longer than ${packNameLength} characters`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matches = packNameRegex.exec(value);
|
||||||
|
if (!matches?.groups) {
|
||||||
|
return "Invalid package name: a pack name must contain only lowercase ASCII letters, ASCII digits, and hyphens";
|
||||||
|
}
|
||||||
|
|
||||||
|
const packPath = join(workspaceFolder.path, matches.groups.name);
|
||||||
|
if (await pathExists(packPath)) {
|
||||||
|
return `A pack already exists at ${packPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
token,
|
||||||
|
);
|
||||||
|
if (!packName) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matches = packNameRegex.exec(packName);
|
||||||
|
if (!matches?.groups) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return fileOption.file;
|
const name = matches.groups.name;
|
||||||
|
const packPath = join(workspaceFolder.path, name);
|
||||||
|
|
||||||
|
if (await pathExists(packPath)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const packYamlPath = join(packPath, "codeql-pack.yml");
|
||||||
|
|
||||||
|
await outputFile(
|
||||||
|
packYamlPath,
|
||||||
|
dumpYaml({
|
||||||
|
name,
|
||||||
|
version: "0.0.0",
|
||||||
|
library: true,
|
||||||
|
extensionTargets: {
|
||||||
|
[`codeql/${databaseItem.language}-all`]: "*",
|
||||||
|
},
|
||||||
|
dataExtensions: ["models/**/*.yml"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return packPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pickNewModelFile(
|
||||||
|
databaseItem: Pick<DatabaseItem, "name">,
|
||||||
|
extensionPackPath: string,
|
||||||
|
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",
|
||||||
|
value: `models/${databaseItem.name.replaceAll("/", ".")}.model.yml`,
|
||||||
|
validateInput: async (value: string): Promise<string | undefined> => {
|
||||||
|
if (value === "") {
|
||||||
|
return "File name must not be empty";
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = resolve(extensionPackPath, value);
|
||||||
|
|
||||||
|
if (await pathExists(path)) {
|
||||||
|
return "File already exists";
|
||||||
|
}
|
||||||
|
|
||||||
|
const notInExtensionPack = relative(extensionPackPath, path).startsWith(
|
||||||
|
"..",
|
||||||
|
);
|
||||||
|
if (notInExtensionPack) {
|
||||||
|
return "File must be in the extension pack";
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchesPattern = dataExtensionPatterns.some((pattern) =>
|
||||||
|
minimatch(value, pattern, { matchBase: true }),
|
||||||
|
);
|
||||||
|
if (!matchesPattern) {
|
||||||
|
return `File must match one of the patterns in 'dataExtensions' in ${qlpackPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
token,
|
||||||
|
);
|
||||||
|
if (!filename) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(extensionPackPath, filename);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
import { CoreCompletedQuery, QueryRunner } from "../queryRunner";
|
import { CoreCompletedQuery, QueryRunner } from "../queryRunner";
|
||||||
import { qlpackOfDatabase } from "../contextual/queryResolver";
|
import { dir } from "tmp-promise";
|
||||||
import { file } from "tmp-promise";
|
|
||||||
import { writeFile } from "fs-extra";
|
import { writeFile } from "fs-extra";
|
||||||
import { dump as dumpYaml } from "js-yaml";
|
import { dump as dumpYaml } from "js-yaml";
|
||||||
import {
|
import {
|
||||||
getOnDiskWorkspaceFolders,
|
getOnDiskWorkspaceFolders,
|
||||||
showAndLogExceptionWithTelemetry,
|
showAndLogExceptionWithTelemetry,
|
||||||
} from "../helpers";
|
} from "../helpers";
|
||||||
import { Logger, TeeLogger } from "../common";
|
import { TeeLogger } from "../common";
|
||||||
import { CancellationToken } from "vscode";
|
import { CancellationToken } from "vscode";
|
||||||
import { CodeQLCliServer } from "../cli";
|
import { CodeQLCliServer } from "../cli";
|
||||||
import { DatabaseItem } from "../local-databases";
|
import { DatabaseItem } from "../local-databases";
|
||||||
import { ProgressCallback } from "../progress";
|
import { ProgressCallback } from "../progress";
|
||||||
|
import { fetchExternalApiQueries } from "./queries";
|
||||||
|
import { QueryResultType } from "../pure/new-messages";
|
||||||
|
import { join } from "path";
|
||||||
import { redactableError } from "../pure/errors";
|
import { redactableError } from "../pure/errors";
|
||||||
|
import { QueryLanguage } from "../common/query-language";
|
||||||
|
|
||||||
export type RunQueryOptions = {
|
export type RunQueryOptions = {
|
||||||
cliServer: Pick<CodeQLCliServer, "resolveQlpacks" | "resolveQueriesInSuite">;
|
cliServer: Pick<CodeQLCliServer, "resolveQlpacks">;
|
||||||
queryRunner: Pick<QueryRunner, "createQueryRun" | "logger">;
|
queryRunner: Pick<QueryRunner, "createQueryRun" | "logger">;
|
||||||
databaseItem: Pick<DatabaseItem, "contents" | "databaseUri" | "language">;
|
databaseItem: Pick<DatabaseItem, "contents" | "databaseUri" | "language">;
|
||||||
queryStorageDir: string;
|
queryStorageDir: string;
|
||||||
logger: Logger;
|
|
||||||
|
|
||||||
progress: ProgressCallback;
|
progress: ProgressCallback;
|
||||||
token: CancellationToken;
|
token: CancellationToken;
|
||||||
@@ -30,54 +32,53 @@ export async function runQuery({
|
|||||||
queryRunner,
|
queryRunner,
|
||||||
databaseItem,
|
databaseItem,
|
||||||
queryStorageDir,
|
queryStorageDir,
|
||||||
logger,
|
|
||||||
progress,
|
progress,
|
||||||
token,
|
token,
|
||||||
}: RunQueryOptions): Promise<CoreCompletedQuery | undefined> {
|
}: RunQueryOptions): Promise<CoreCompletedQuery | undefined> {
|
||||||
const qlpacks = await qlpackOfDatabase(cliServer, databaseItem);
|
// The below code is temporary to allow for rapid prototyping of the queries. Once the queries are stabilized, we will
|
||||||
|
// move these queries into the `github/codeql` repository and use them like any other contextual (e.g. AST) queries.
|
||||||
|
// This is intentionally not pretty code, as it will be removed soon.
|
||||||
|
// For a reference of what this should do in the future, see the previous implementation in
|
||||||
|
// https://github.com/github/vscode-codeql/blob/089d3566ef0bc67d9b7cc66e8fd6740b31c1c0b0/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts#L33-L72
|
||||||
|
|
||||||
const packsToSearch = [qlpacks.dbschemePack];
|
const query = fetchExternalApiQueries[databaseItem.language as QueryLanguage];
|
||||||
if (qlpacks.queryPack) {
|
if (!query) {
|
||||||
packsToSearch.push(qlpacks.queryPack);
|
void showAndLogExceptionWithTelemetry(
|
||||||
|
redactableError`No external API usage query found for language ${databaseItem.language}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const suiteFile = (
|
const queryDir = (await dir({ unsafeCleanup: true })).path;
|
||||||
await file({
|
const queryFile = join(queryDir, "FetchExternalApis.ql");
|
||||||
postfix: ".qls",
|
await writeFile(queryFile, query.mainQuery, "utf8");
|
||||||
})
|
|
||||||
).path;
|
if (query.dependencies) {
|
||||||
const suiteYaml = [];
|
for (const [filename, contents] of Object.entries(query.dependencies)) {
|
||||||
for (const qlpack of packsToSearch) {
|
const dependencyFile = join(queryDir, filename);
|
||||||
suiteYaml.push({
|
await writeFile(dependencyFile, contents, "utf8");
|
||||||
from: qlpack,
|
}
|
||||||
queries: ".",
|
|
||||||
include: {
|
|
||||||
id: `${databaseItem.language}/telemetry/fetch-external-apis`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
await writeFile(suiteFile, dumpYaml(suiteYaml), "utf8");
|
|
||||||
|
const syntheticQueryPack = {
|
||||||
|
name: "codeql/external-api-usage",
|
||||||
|
version: "0.0.0",
|
||||||
|
dependencies: {
|
||||||
|
[`codeql/${databaseItem.language}-all`]: "*",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const qlpackFile = join(queryDir, "codeql-pack.yml");
|
||||||
|
await writeFile(qlpackFile, dumpYaml(syntheticQueryPack), "utf8");
|
||||||
|
|
||||||
const additionalPacks = getOnDiskWorkspaceFolders();
|
const additionalPacks = getOnDiskWorkspaceFolders();
|
||||||
const extensionPacks = Object.keys(
|
const extensionPacks = Object.keys(
|
||||||
await cliServer.resolveQlpacks(additionalPacks, true),
|
await cliServer.resolveQlpacks(additionalPacks, true),
|
||||||
);
|
);
|
||||||
|
|
||||||
const queries = await cliServer.resolveQueriesInSuite(
|
|
||||||
suiteFile,
|
|
||||||
getOnDiskWorkspaceFolders(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (queries.length !== 1) {
|
|
||||||
void logger.log(`Expected exactly one query, got ${queries.length}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = queries[0];
|
|
||||||
|
|
||||||
const queryRun = queryRunner.createQueryRun(
|
const queryRun = queryRunner.createQueryRun(
|
||||||
databaseItem.databaseUri.fsPath,
|
databaseItem.databaseUri.fsPath,
|
||||||
{ queryPath: query, quickEvalPosition: undefined },
|
{ queryPath: queryFile, quickEvalPosition: undefined },
|
||||||
false,
|
false,
|
||||||
getOnDiskWorkspaceFolders(),
|
getOnDiskWorkspaceFolders(),
|
||||||
extensionPacks,
|
extensionPacks,
|
||||||
@@ -86,11 +87,22 @@ export async function runQuery({
|
|||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
return queryRun.evaluate(
|
const completedQuery = await queryRun.evaluate(
|
||||||
progress,
|
progress,
|
||||||
token,
|
token,
|
||||||
new TeeLogger(queryRunner.logger, queryRun.outputDir.logPath),
|
new TeeLogger(queryRunner.logger, queryRun.outputDir.logPath),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (completedQuery.resultType !== QueryResultType.SUCCESS) {
|
||||||
|
void showAndLogExceptionWithTelemetry(
|
||||||
|
redactableError`External API usage query failed: ${
|
||||||
|
completedQuery.message ?? "No message"
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return completedQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GetResultsOptions = {
|
export type GetResultsOptions = {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { join } from "path";
|
|||||||
import { QueryRunner } from "../queryRunner";
|
import { QueryRunner } from "../queryRunner";
|
||||||
import { CodeQLCliServer } from "../cli";
|
import { CodeQLCliServer } from "../cli";
|
||||||
import { TeeLogger } from "../common";
|
import { TeeLogger } from "../common";
|
||||||
import { extensiblePredicateDefinitions } from "./yaml";
|
import { extensiblePredicateDefinitions } from "./predicates";
|
||||||
import { ProgressCallback } from "../progress";
|
import { ProgressCallback } from "../progress";
|
||||||
import {
|
import {
|
||||||
getOnDiskWorkspaceFolders,
|
getOnDiskWorkspaceFolders,
|
||||||
|
|||||||
138
extensions/ql-vscode/src/data-extensions-editor/predicates.ts
Normal file
138
extensions/ql-vscode/src/data-extensions-editor/predicates.ts
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import { ExternalApiUsage } from "./external-api-usage";
|
||||||
|
import {
|
||||||
|
ModeledMethod,
|
||||||
|
ModeledMethodType,
|
||||||
|
ModeledMethodWithSignature,
|
||||||
|
} from "./modeled-method";
|
||||||
|
|
||||||
|
export type ExternalApiUsageByType = {
|
||||||
|
externalApiUsage: ExternalApiUsage;
|
||||||
|
modeledMethod: ModeledMethod;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ExtensiblePredicateDefinition = {
|
||||||
|
extensiblePredicate: string;
|
||||||
|
generateMethodDefinition: (method: ExternalApiUsageByType) => Tuple[];
|
||||||
|
readModeledMethod: (row: Tuple[]) => ModeledMethodWithSignature;
|
||||||
|
|
||||||
|
supportedKinds?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type Tuple = boolean | number | string;
|
||||||
|
|
||||||
|
function readRowToMethod(row: Tuple[]): string {
|
||||||
|
return `${row[0]}.${row[1]}#${row[3]}${row[4]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const extensiblePredicateDefinitions: Record<
|
||||||
|
Exclude<ModeledMethodType, "none">,
|
||||||
|
ExtensiblePredicateDefinition
|
||||||
|
> = {
|
||||||
|
source: {
|
||||||
|
extensiblePredicate: "sourceModel",
|
||||||
|
// extensible predicate sourceModel(
|
||||||
|
// string package, string type, boolean subtypes, string name, string signature, string ext,
|
||||||
|
// string output, string kind, string provenance
|
||||||
|
// );
|
||||||
|
generateMethodDefinition: (method) => [
|
||||||
|
method.externalApiUsage.packageName,
|
||||||
|
method.externalApiUsage.typeName,
|
||||||
|
true,
|
||||||
|
method.externalApiUsage.methodName,
|
||||||
|
method.externalApiUsage.methodParameters,
|
||||||
|
"",
|
||||||
|
method.modeledMethod.output,
|
||||||
|
method.modeledMethod.kind,
|
||||||
|
"manual",
|
||||||
|
],
|
||||||
|
readModeledMethod: (row) => ({
|
||||||
|
signature: readRowToMethod(row),
|
||||||
|
modeledMethod: {
|
||||||
|
type: "source",
|
||||||
|
input: "",
|
||||||
|
output: row[6] as string,
|
||||||
|
kind: row[7] as string,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
supportedKinds: ["remote"],
|
||||||
|
},
|
||||||
|
sink: {
|
||||||
|
extensiblePredicate: "sinkModel",
|
||||||
|
// extensible predicate sinkModel(
|
||||||
|
// string package, string type, boolean subtypes, string name, string signature, string ext,
|
||||||
|
// string input, string kind, string provenance
|
||||||
|
// );
|
||||||
|
generateMethodDefinition: (method) => [
|
||||||
|
method.externalApiUsage.packageName,
|
||||||
|
method.externalApiUsage.typeName,
|
||||||
|
true,
|
||||||
|
method.externalApiUsage.methodName,
|
||||||
|
method.externalApiUsage.methodParameters,
|
||||||
|
"",
|
||||||
|
method.modeledMethod.input,
|
||||||
|
method.modeledMethod.kind,
|
||||||
|
"manual",
|
||||||
|
],
|
||||||
|
readModeledMethod: (row) => ({
|
||||||
|
signature: readRowToMethod(row),
|
||||||
|
modeledMethod: {
|
||||||
|
type: "sink",
|
||||||
|
input: row[6] as string,
|
||||||
|
output: "",
|
||||||
|
kind: row[7] as string,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
supportedKinds: ["sql", "xss", "logging"],
|
||||||
|
},
|
||||||
|
summary: {
|
||||||
|
extensiblePredicate: "summaryModel",
|
||||||
|
// extensible predicate summaryModel(
|
||||||
|
// string package, string type, boolean subtypes, string name, string signature, string ext,
|
||||||
|
// string input, string output, string kind, string provenance
|
||||||
|
// );
|
||||||
|
generateMethodDefinition: (method) => [
|
||||||
|
method.externalApiUsage.packageName,
|
||||||
|
method.externalApiUsage.typeName,
|
||||||
|
true,
|
||||||
|
method.externalApiUsage.methodName,
|
||||||
|
method.externalApiUsage.methodParameters,
|
||||||
|
"",
|
||||||
|
method.modeledMethod.input,
|
||||||
|
method.modeledMethod.output,
|
||||||
|
method.modeledMethod.kind,
|
||||||
|
"manual",
|
||||||
|
],
|
||||||
|
readModeledMethod: (row) => ({
|
||||||
|
signature: readRowToMethod(row),
|
||||||
|
modeledMethod: {
|
||||||
|
type: "summary",
|
||||||
|
input: row[6] as string,
|
||||||
|
output: row[7] as string,
|
||||||
|
kind: row[8] as string,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
supportedKinds: ["taint", "value"],
|
||||||
|
},
|
||||||
|
neutral: {
|
||||||
|
extensiblePredicate: "neutralModel",
|
||||||
|
// extensible predicate neutralModel(
|
||||||
|
// string package, string type, string name, string signature, string provenance
|
||||||
|
// );
|
||||||
|
generateMethodDefinition: (method) => [
|
||||||
|
method.externalApiUsage.packageName,
|
||||||
|
method.externalApiUsage.typeName,
|
||||||
|
method.externalApiUsage.methodName,
|
||||||
|
method.externalApiUsage.methodParameters,
|
||||||
|
"manual",
|
||||||
|
],
|
||||||
|
readModeledMethod: (row) => ({
|
||||||
|
signature: `${row[0]}.${row[1]}#${row[2]}${row[3]}`,
|
||||||
|
modeledMethod: {
|
||||||
|
type: "neutral",
|
||||||
|
input: "",
|
||||||
|
output: "",
|
||||||
|
kind: "",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
import { Query } from "./query";
|
||||||
|
|
||||||
|
export const fetchExternalApisQuery: Query = {
|
||||||
|
mainQuery: `/**
|
||||||
|
* @name Usage of APIs coming from external libraries
|
||||||
|
* @description A list of 3rd party APIs used in the codebase.
|
||||||
|
* @tags telemetry
|
||||||
|
* @id cs/telemetry/fetch-external-apis
|
||||||
|
*/
|
||||||
|
|
||||||
|
import csharp
|
||||||
|
import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
|
||||||
|
import ExternalApi
|
||||||
|
|
||||||
|
private Call aUsage(ExternalApi api) {
|
||||||
|
result.getTarget().getUnboundDeclaration() = api
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSupported(ExternalApi api) {
|
||||||
|
api.isSupported() and result = true
|
||||||
|
or
|
||||||
|
not api.isSupported() and
|
||||||
|
result = false
|
||||||
|
}
|
||||||
|
|
||||||
|
from ExternalApi api, string apiName, boolean supported, Call usage
|
||||||
|
where
|
||||||
|
apiName = api.getApiName() and
|
||||||
|
supported = isSupported(api) and
|
||||||
|
usage = aUsage(api)
|
||||||
|
select apiName, supported, usage
|
||||||
|
`,
|
||||||
|
dependencies: {
|
||||||
|
"ExternalApi.qll": `/** Provides classes and predicates related to handling APIs from external libraries. */
|
||||||
|
|
||||||
|
private import csharp
|
||||||
|
private import dotnet
|
||||||
|
private import semmle.code.csharp.dispatch.Dispatch
|
||||||
|
private import semmle.code.csharp.dataflow.ExternalFlow
|
||||||
|
private import semmle.code.csharp.dataflow.FlowSummary
|
||||||
|
private import semmle.code.csharp.dataflow.internal.DataFlowImplCommon as DataFlowImplCommon
|
||||||
|
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
|
||||||
|
private import semmle.code.csharp.dataflow.internal.DataFlowDispatch as DataFlowDispatch
|
||||||
|
private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
|
||||||
|
private import semmle.code.csharp.dataflow.internal.TaintTrackingPrivate
|
||||||
|
private import semmle.code.csharp.security.dataflow.flowsources.Remote
|
||||||
|
|
||||||
|
pragma[nomagic]
|
||||||
|
private predicate isTestNamespace(Namespace ns) {
|
||||||
|
ns.getFullName()
|
||||||
|
.matches([
|
||||||
|
"NUnit.Framework%", "Xunit%", "Microsoft.VisualStudio.TestTools.UnitTesting%", "Moq%"
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A test library.
|
||||||
|
*/
|
||||||
|
class TestLibrary extends RefType {
|
||||||
|
TestLibrary() { isTestNamespace(this.getNamespace()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds if the given callable is not worth supporting. */
|
||||||
|
private predicate isUninteresting(DotNet::Callable c) {
|
||||||
|
c.getDeclaringType() instanceof TestLibrary or
|
||||||
|
c.(Constructor).isParameterless()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An external API from either the C# Standard Library or a 3rd party library.
|
||||||
|
*/
|
||||||
|
class ExternalApi extends DotNet::Callable {
|
||||||
|
ExternalApi() {
|
||||||
|
this.isUnboundDeclaration() and
|
||||||
|
this.fromLibrary() and
|
||||||
|
this.(Modifiable).isEffectivelyPublic() and
|
||||||
|
not isUninteresting(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the unbound type, name and parameter types of this API.
|
||||||
|
*/
|
||||||
|
bindingset[this]
|
||||||
|
private string getSignature() {
|
||||||
|
result =
|
||||||
|
this.getDeclaringType().getUnboundDeclaration() + "." + this.getName() + "(" +
|
||||||
|
parameterQualifiedTypeNamesToString(this) + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the namespace of this API.
|
||||||
|
*/
|
||||||
|
bindingset[this]
|
||||||
|
string getNamespace() { this.getDeclaringType().hasQualifiedName(result, _) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the namespace and signature of this API.
|
||||||
|
*/
|
||||||
|
bindingset[this]
|
||||||
|
string getApiName() { result = this.getNamespace() + "#" + this.getSignature() }
|
||||||
|
|
||||||
|
/** Gets a node that is an input to a call to this API. */
|
||||||
|
private ArgumentNode getAnInput() {
|
||||||
|
result
|
||||||
|
.getCall()
|
||||||
|
.(DataFlowDispatch::NonDelegateDataFlowCall)
|
||||||
|
.getATarget(_)
|
||||||
|
.getUnboundDeclaration() = this
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets a node that is an output from a call to this API. */
|
||||||
|
private DataFlow::Node getAnOutput() {
|
||||||
|
exists(
|
||||||
|
Call c, DataFlowDispatch::NonDelegateDataFlowCall dc, DataFlowImplCommon::ReturnKindExt ret
|
||||||
|
|
|
||||||
|
dc.getDispatchCall().getCall() = c and
|
||||||
|
c.getTarget().getUnboundDeclaration() = this
|
||||||
|
|
|
||||||
|
result = ret.getAnOutNode(dc)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds if this API has a supported summary. */
|
||||||
|
pragma[nomagic]
|
||||||
|
predicate hasSummary() {
|
||||||
|
this instanceof SummarizedCallable
|
||||||
|
or
|
||||||
|
defaultAdditionalTaintStep(this.getAnInput(), _)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds if this API is a known source. */
|
||||||
|
pragma[nomagic]
|
||||||
|
predicate isSource() {
|
||||||
|
this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds if this API is a known sink. */
|
||||||
|
pragma[nomagic]
|
||||||
|
predicate isSink() { sinkNode(this.getAnInput(), _) }
|
||||||
|
|
||||||
|
/** Holds if this API is a known neutral. */
|
||||||
|
pragma[nomagic]
|
||||||
|
predicate isNeutral() { this instanceof FlowSummaryImpl::Public::NeutralCallable }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if this API is supported by existing CodeQL libraries, that is, it is either a
|
||||||
|
* recognized source, sink or neutral or it has a flow summary.
|
||||||
|
*/
|
||||||
|
predicate isSupported() {
|
||||||
|
this.hasSummary() or this.isSource() or this.isSink() or this.isNeutral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the limit for the number of results produced by a telemetry query.
|
||||||
|
*/
|
||||||
|
int resultLimit() { result = 1000 }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if it is relevant to count usages of "api".
|
||||||
|
*/
|
||||||
|
signature predicate relevantApi(ExternalApi api);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a predicate to count relevant API usages, this module provides a predicate
|
||||||
|
* for restricting the number or returned results based on a certain limit.
|
||||||
|
*/
|
||||||
|
module Results<relevantApi/1 getRelevantUsages> {
|
||||||
|
private int getUsages(string apiName) {
|
||||||
|
result =
|
||||||
|
strictcount(Call c, ExternalApi api |
|
||||||
|
c.getTarget().getUnboundDeclaration() = api and
|
||||||
|
apiName = api.getApiName() and
|
||||||
|
getRelevantUsages(api)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getOrder(string apiName) {
|
||||||
|
apiName =
|
||||||
|
rank[result](string name, int usages |
|
||||||
|
usages = getUsages(name)
|
||||||
|
|
|
||||||
|
name order by usages desc, name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if there exists an API with "apiName" that is being used "usages" times
|
||||||
|
* and if it is in the top results (guarded by resultLimit).
|
||||||
|
*/
|
||||||
|
predicate restrict(string apiName, int usages) {
|
||||||
|
usages = getUsages(apiName) and
|
||||||
|
getOrder(apiName) <= resultLimit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { fetchExternalApisQuery as csharpFetchExternalApisQuery } from "./csharp";
|
||||||
|
import { fetchExternalApisQuery as javaFetchExternalApisQuery } from "./java";
|
||||||
|
import { Query } from "./query";
|
||||||
|
import { QueryLanguage } from "../../common/query-language";
|
||||||
|
|
||||||
|
export const fetchExternalApiQueries: Partial<Record<QueryLanguage, Query>> = {
|
||||||
|
[QueryLanguage.CSharp]: csharpFetchExternalApisQuery,
|
||||||
|
[QueryLanguage.Java]: javaFetchExternalApisQuery,
|
||||||
|
};
|
||||||
179
extensions/ql-vscode/src/data-extensions-editor/queries/java.ts
Normal file
179
extensions/ql-vscode/src/data-extensions-editor/queries/java.ts
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
import { Query } from "./query";
|
||||||
|
|
||||||
|
export const fetchExternalApisQuery: Query = {
|
||||||
|
mainQuery: `/**
|
||||||
|
* @name Usage of APIs coming from external libraries
|
||||||
|
* @description A list of 3rd party APIs used in the codebase. Excludes test and generated code.
|
||||||
|
* @tags telemetry
|
||||||
|
* @id java/telemetry/fetch-external-apis
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import semmle.code.java.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
|
||||||
|
import ExternalApi
|
||||||
|
|
||||||
|
private Call aUsage(ExternalApi api) {
|
||||||
|
result.getCallee().getSourceDeclaration() = api and
|
||||||
|
not result.getFile() instanceof GeneratedFile
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSupported(ExternalApi api) {
|
||||||
|
api.isSupported() and result = true
|
||||||
|
or
|
||||||
|
not api.isSupported() and result = false
|
||||||
|
}
|
||||||
|
|
||||||
|
from ExternalApi api, string apiName, boolean supported, Call usage
|
||||||
|
where
|
||||||
|
apiName = api.getApiName() and
|
||||||
|
supported = isSupported(api) and
|
||||||
|
usage = aUsage(api)
|
||||||
|
select apiName, supported, usage
|
||||||
|
`,
|
||||||
|
dependencies: {
|
||||||
|
"ExternalApi.qll": `/** Provides classes and predicates related to handling APIs from external libraries. */
|
||||||
|
|
||||||
|
private import java
|
||||||
|
private import semmle.code.java.dataflow.DataFlow
|
||||||
|
private import semmle.code.java.dataflow.ExternalFlow
|
||||||
|
private import semmle.code.java.dataflow.FlowSources
|
||||||
|
private import semmle.code.java.dataflow.FlowSummary
|
||||||
|
private import semmle.code.java.dataflow.internal.DataFlowPrivate
|
||||||
|
private import semmle.code.java.dataflow.TaintTracking
|
||||||
|
|
||||||
|
pragma[nomagic]
|
||||||
|
private predicate isTestPackage(Package p) {
|
||||||
|
p.getName()
|
||||||
|
.matches([
|
||||||
|
"org.junit%", "junit.%", "org.mockito%", "org.assertj%",
|
||||||
|
"com.github.tomakehurst.wiremock%", "org.hamcrest%", "org.springframework.test.%",
|
||||||
|
"org.springframework.mock.%", "org.springframework.boot.test.%", "reactor.test%",
|
||||||
|
"org.xmlunit%", "org.testcontainers.%", "org.opentest4j%", "org.mockserver%",
|
||||||
|
"org.powermock%", "org.skyscreamer.jsonassert%", "org.rnorth.visibleassertions",
|
||||||
|
"org.openqa.selenium%", "com.gargoylesoftware.htmlunit%", "org.jboss.arquillian.testng%",
|
||||||
|
"org.testng%"
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A test library.
|
||||||
|
*/
|
||||||
|
private class TestLibrary extends RefType {
|
||||||
|
TestLibrary() { isTestPackage(this.getPackage()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private string containerAsJar(Container container) {
|
||||||
|
if container instanceof JarFile then result = container.getBaseName() else result = "rt.jar"
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds if the given callable is not worth supporting. */
|
||||||
|
private predicate isUninteresting(Callable c) {
|
||||||
|
c.getDeclaringType() instanceof TestLibrary or
|
||||||
|
c.(Constructor).isParameterless()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An external API from either the Standard Library or a 3rd party library.
|
||||||
|
*/
|
||||||
|
class ExternalApi extends Callable {
|
||||||
|
ExternalApi() { not this.fromSource() and not isUninteresting(this) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets information about the external API in the form expected by the MaD modeling framework.
|
||||||
|
*/
|
||||||
|
string getApiName() {
|
||||||
|
result =
|
||||||
|
this.getDeclaringType().getPackage() + "." + this.getDeclaringType().getSourceDeclaration() +
|
||||||
|
"#" + this.getName() + paramsString(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the jar file containing this API. Normalizes the Java Runtime to "rt.jar" despite the presence of modules.
|
||||||
|
*/
|
||||||
|
string jarContainer() { result = containerAsJar(this.getCompilationUnit().getParentContainer*()) }
|
||||||
|
|
||||||
|
/** Gets a node that is an input to a call to this API. */
|
||||||
|
private DataFlow::Node getAnInput() {
|
||||||
|
exists(Call call | call.getCallee().getSourceDeclaration() = this |
|
||||||
|
result.asExpr().(Argument).getCall() = call or
|
||||||
|
result.(ArgumentNode).getCall().asCall() = call
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets a node that is an output from a call to this API. */
|
||||||
|
private DataFlow::Node getAnOutput() {
|
||||||
|
exists(Call call | call.getCallee().getSourceDeclaration() = this |
|
||||||
|
result.asExpr() = call or
|
||||||
|
result.(DataFlow::PostUpdateNode).getPreUpdateNode().(ArgumentNode).getCall().asCall() = call
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds if this API has a supported summary. */
|
||||||
|
pragma[nomagic]
|
||||||
|
predicate hasSummary() {
|
||||||
|
this = any(SummarizedCallable sc).asCallable() or
|
||||||
|
TaintTracking::localAdditionalTaintStep(this.getAnInput(), _)
|
||||||
|
}
|
||||||
|
|
||||||
|
pragma[nomagic]
|
||||||
|
predicate isSource() {
|
||||||
|
this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds if this API is a known sink. */
|
||||||
|
pragma[nomagic]
|
||||||
|
predicate isSink() { sinkNode(this.getAnInput(), _) }
|
||||||
|
|
||||||
|
/** Holds if this API is supported by existing CodeQL libraries, that is, it is either a recognized source or sink or has a flow summary. */
|
||||||
|
predicate isSupported() { this.hasSummary() or this.isSource() or this.isSink() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** DEPRECATED: Alias for ExternalApi */
|
||||||
|
deprecated class ExternalAPI = ExternalApi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the limit for the number of results produced by a telemetry query.
|
||||||
|
*/
|
||||||
|
int resultLimit() { result = 1000 }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if it is relevant to count usages of \`api\`.
|
||||||
|
*/
|
||||||
|
signature predicate relevantApi(ExternalApi api);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a predicate to count relevant API usages, this module provides a predicate
|
||||||
|
* for restricting the number or returned results based on a certain limit.
|
||||||
|
*/
|
||||||
|
module Results<relevantApi/1 getRelevantUsages> {
|
||||||
|
private int getUsages(string apiName) {
|
||||||
|
result =
|
||||||
|
strictcount(Call c, ExternalApi api |
|
||||||
|
c.getCallee().getSourceDeclaration() = api and
|
||||||
|
not c.getFile() instanceof GeneratedFile and
|
||||||
|
apiName = api.getApiName() and
|
||||||
|
getRelevantUsages(api)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getOrder(string apiInfo) {
|
||||||
|
apiInfo =
|
||||||
|
rank[result](string info, int usages |
|
||||||
|
usages = getUsages(info)
|
||||||
|
|
|
||||||
|
info order by usages desc, info
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if there exists an API with \`apiName\` that is being used \`usages\` times
|
||||||
|
* and if it is in the top results (guarded by resultLimit).
|
||||||
|
*/
|
||||||
|
predicate restrict(string apiName, int usages) {
|
||||||
|
usages = getUsages(apiName) and
|
||||||
|
getOrder(apiName) <= resultLimit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export type Query = {
|
||||||
|
mainQuery: string;
|
||||||
|
dependencies?: {
|
||||||
|
[filename: string]: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
ModeledMethodType,
|
ModeledMethodType,
|
||||||
ModeledMethodWithSignature,
|
ModeledMethodWithSignature,
|
||||||
} from "./modeled-method";
|
} from "./modeled-method";
|
||||||
|
import { extensiblePredicateDefinitions } from "./predicates";
|
||||||
|
|
||||||
import * as dataSchemaJson from "./data-schema.json";
|
import * as dataSchemaJson from "./data-schema.json";
|
||||||
|
|
||||||
@@ -23,120 +24,6 @@ type ExtensiblePredicateDefinition = {
|
|||||||
readModeledMethod: (row: any[]) => ModeledMethodWithSignature;
|
readModeledMethod: (row: any[]) => ModeledMethodWithSignature;
|
||||||
};
|
};
|
||||||
|
|
||||||
function readRowToMethod(row: any[]): string {
|
|
||||||
return `${row[0]}.${row[1]}#${row[3]}${row[4]}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const extensiblePredicateDefinitions: Record<
|
|
||||||
Exclude<ModeledMethodType, "none">,
|
|
||||||
ExtensiblePredicateDefinition
|
|
||||||
> = {
|
|
||||||
source: {
|
|
||||||
extensiblePredicate: "sourceModel",
|
|
||||||
// extensible predicate sourceModel(
|
|
||||||
// string package, string type, boolean subtypes, string name, string signature, string ext,
|
|
||||||
// string output, string kind, string provenance
|
|
||||||
// );
|
|
||||||
generateMethodDefinition: (method) => [
|
|
||||||
method.externalApiUsage.packageName,
|
|
||||||
method.externalApiUsage.typeName,
|
|
||||||
true,
|
|
||||||
method.externalApiUsage.methodName,
|
|
||||||
method.externalApiUsage.methodParameters,
|
|
||||||
"",
|
|
||||||
method.modeledMethod.output,
|
|
||||||
method.modeledMethod.kind,
|
|
||||||
"manual",
|
|
||||||
],
|
|
||||||
readModeledMethod: (row) => ({
|
|
||||||
signature: readRowToMethod(row),
|
|
||||||
modeledMethod: {
|
|
||||||
type: "source",
|
|
||||||
input: "",
|
|
||||||
output: row[6],
|
|
||||||
kind: row[7],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
sink: {
|
|
||||||
extensiblePredicate: "sinkModel",
|
|
||||||
// extensible predicate sinkModel(
|
|
||||||
// string package, string type, boolean subtypes, string name, string signature, string ext,
|
|
||||||
// string input, string kind, string provenance
|
|
||||||
// );
|
|
||||||
generateMethodDefinition: (method) => [
|
|
||||||
method.externalApiUsage.packageName,
|
|
||||||
method.externalApiUsage.typeName,
|
|
||||||
true,
|
|
||||||
method.externalApiUsage.methodName,
|
|
||||||
method.externalApiUsage.methodParameters,
|
|
||||||
"",
|
|
||||||
method.modeledMethod.input,
|
|
||||||
method.modeledMethod.kind,
|
|
||||||
"manual",
|
|
||||||
],
|
|
||||||
readModeledMethod: (row) => ({
|
|
||||||
signature: readRowToMethod(row),
|
|
||||||
modeledMethod: {
|
|
||||||
type: "sink",
|
|
||||||
input: row[6],
|
|
||||||
output: "",
|
|
||||||
kind: row[7],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
summary: {
|
|
||||||
extensiblePredicate: "summaryModel",
|
|
||||||
// extensible predicate summaryModel(
|
|
||||||
// string package, string type, boolean subtypes, string name, string signature, string ext,
|
|
||||||
// string input, string output, string kind, string provenance
|
|
||||||
// );
|
|
||||||
generateMethodDefinition: (method) => [
|
|
||||||
method.externalApiUsage.packageName,
|
|
||||||
method.externalApiUsage.typeName,
|
|
||||||
true,
|
|
||||||
method.externalApiUsage.methodName,
|
|
||||||
method.externalApiUsage.methodParameters,
|
|
||||||
"",
|
|
||||||
method.modeledMethod.input,
|
|
||||||
method.modeledMethod.output,
|
|
||||||
method.modeledMethod.kind,
|
|
||||||
"manual",
|
|
||||||
],
|
|
||||||
readModeledMethod: (row) => ({
|
|
||||||
signature: readRowToMethod(row),
|
|
||||||
modeledMethod: {
|
|
||||||
type: "summary",
|
|
||||||
input: row[6],
|
|
||||||
output: row[7],
|
|
||||||
kind: row[8],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
neutral: {
|
|
||||||
extensiblePredicate: "neutralModel",
|
|
||||||
// extensible predicate neutralModel(
|
|
||||||
// string package, string type, string name, string signature, string provenance
|
|
||||||
// );
|
|
||||||
generateMethodDefinition: (method) => [
|
|
||||||
method.externalApiUsage.packageName,
|
|
||||||
method.externalApiUsage.typeName,
|
|
||||||
method.externalApiUsage.methodName,
|
|
||||||
method.externalApiUsage.methodParameters,
|
|
||||||
"manual",
|
|
||||||
],
|
|
||||||
readModeledMethod: (row) => ({
|
|
||||||
signature: `${row[0]}.${row[1]}#${row[2]}${row[3]}`,
|
|
||||||
modeledMethod: {
|
|
||||||
type: "neutral",
|
|
||||||
input: "",
|
|
||||||
output: "",
|
|
||||||
kind: "",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function createDataProperty(
|
function createDataProperty(
|
||||||
methods: ExternalApiUsageByType[],
|
methods: ExternalApiUsageByType[],
|
||||||
definition: ExtensiblePredicateDefinition,
|
definition: ExtensiblePredicateDefinition,
|
||||||
@@ -156,6 +43,7 @@ function createDataProperty(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createDataExtensionYaml(
|
export function createDataExtensionYaml(
|
||||||
|
language: string,
|
||||||
externalApiUsages: ExternalApiUsage[],
|
externalApiUsages: ExternalApiUsage[],
|
||||||
modeledMethods: Record<string, ModeledMethod>,
|
modeledMethods: Record<string, ModeledMethod>,
|
||||||
) {
|
) {
|
||||||
@@ -182,7 +70,7 @@ export function createDataExtensionYaml(
|
|||||||
|
|
||||||
const extensions = Object.entries(extensiblePredicateDefinitions).map(
|
const extensions = Object.entries(extensiblePredicateDefinitions).map(
|
||||||
([type, definition]) => ` - addsTo:
|
([type, definition]) => ` - addsTo:
|
||||||
pack: codeql/java-all
|
pack: codeql/${language}-all
|
||||||
extensible: ${definition.extensiblePredicate}
|
extensible: ${definition.extensiblePredicate}
|
||||||
data:${createDataProperty(
|
data:${createDataProperty(
|
||||||
methodsByType[type as Exclude<ModeledMethodType, "none">],
|
methodsByType[type as Exclude<ModeledMethodType, "none">],
|
||||||
|
|||||||
@@ -34,11 +34,11 @@ import { DatabasePanelCommands } from "../../common/commands";
|
|||||||
import { App } from "../../common/app";
|
import { App } from "../../common/app";
|
||||||
|
|
||||||
export interface RemoteDatabaseQuickPickItem extends QuickPickItem {
|
export interface RemoteDatabaseQuickPickItem extends QuickPickItem {
|
||||||
kind: string;
|
remoteDatabaseKind: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AddListQuickPickItem extends QuickPickItem {
|
export interface AddListQuickPickItem extends QuickPickItem {
|
||||||
kind: DbListKind;
|
databaseKind: DbListKind;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DbPanel extends DisposableObject {
|
export class DbPanel extends DisposableObject {
|
||||||
@@ -113,19 +113,19 @@ export class DbPanel extends DisposableObject {
|
|||||||
) {
|
) {
|
||||||
await this.addNewRemoteRepo(highlightedItem.parentListName);
|
await this.addNewRemoteRepo(highlightedItem.parentListName);
|
||||||
} else {
|
} else {
|
||||||
const quickPickItems = [
|
const quickPickItems: RemoteDatabaseQuickPickItem[] = [
|
||||||
{
|
{
|
||||||
label: "$(repo) From a GitHub repository",
|
label: "$(repo) From a GitHub repository",
|
||||||
detail: "Add a variant analysis repository from GitHub",
|
detail: "Add a variant analysis repository from GitHub",
|
||||||
alwaysShow: true,
|
alwaysShow: true,
|
||||||
kind: "repo",
|
remoteDatabaseKind: "repo",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "$(organization) All repositories of a GitHub org or owner",
|
label: "$(organization) All repositories of a GitHub org or owner",
|
||||||
detail:
|
detail:
|
||||||
"Add a variant analysis list of repositories from a GitHub organization/owner",
|
"Add a variant analysis list of repositories from a GitHub organization/owner",
|
||||||
alwaysShow: true,
|
alwaysShow: true,
|
||||||
kind: "owner",
|
remoteDatabaseKind: "owner",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const databaseKind =
|
const databaseKind =
|
||||||
@@ -142,9 +142,9 @@ export class DbPanel extends DisposableObject {
|
|||||||
// We set 'true' to make this a silent exception.
|
// We set 'true' to make this a silent exception.
|
||||||
throw new UserCancellationException("No repository selected", true);
|
throw new UserCancellationException("No repository selected", true);
|
||||||
}
|
}
|
||||||
if (databaseKind.kind === "repo") {
|
if (databaseKind.remoteDatabaseKind === "repo") {
|
||||||
await this.addNewRemoteRepo();
|
await this.addNewRemoteRepo();
|
||||||
} else if (databaseKind.kind === "owner") {
|
} else if (databaseKind.remoteDatabaseKind === "owner") {
|
||||||
await this.addNewRemoteOwner();
|
await this.addNewRemoteOwner();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,7 +177,13 @@ function getCommands(
|
|||||||
cliServer.restartCliServer();
|
cliServer.restartCliServer();
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
queryRunner.restartQueryServer(progress, token),
|
queryRunner.restartQueryServer(progress, token),
|
||||||
ideServer.restart(),
|
async () => {
|
||||||
|
if (ideServer.isRunning()) {
|
||||||
|
await ideServer.restart();
|
||||||
|
} else {
|
||||||
|
await ideServer.start();
|
||||||
|
}
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
void showAndLogInformationMessage("CodeQL Query Server restarted.", {
|
void showAndLogInformationMessage("CodeQL Query Server restarted.", {
|
||||||
outputLogger: queryServerLogger,
|
outputLogger: queryServerLogger,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
} from "fs-extra";
|
} from "fs-extra";
|
||||||
import { glob } from "glob";
|
import { glob } from "glob";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { join, basename } from "path";
|
import { join, basename, dirname } from "path";
|
||||||
import { dirSync } from "tmp-promise";
|
import { dirSync } from "tmp-promise";
|
||||||
import {
|
import {
|
||||||
ExtensionContext,
|
ExtensionContext,
|
||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
window as Window,
|
window as Window,
|
||||||
workspace,
|
workspace,
|
||||||
env,
|
env,
|
||||||
|
WorkspaceFolder,
|
||||||
} from "vscode";
|
} from "vscode";
|
||||||
import { CodeQLCliServer, QlpacksInfo } from "./cli";
|
import { CodeQLCliServer, QlpacksInfo } from "./cli";
|
||||||
import { UserCancellationException } from "./progress";
|
import { UserCancellationException } from "./progress";
|
||||||
@@ -249,16 +250,21 @@ export async function showInformationMessageWithAction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Gets all active workspace folders that are on the filesystem. */
|
/** Gets all active workspace folders that are on the filesystem. */
|
||||||
export function getOnDiskWorkspaceFolders() {
|
export function getOnDiskWorkspaceFoldersObjects() {
|
||||||
const workspaceFolders = workspace.workspaceFolders || [];
|
const workspaceFolders = workspace.workspaceFolders || [];
|
||||||
const diskWorkspaceFolders: string[] = [];
|
const diskWorkspaceFolders: WorkspaceFolder[] = [];
|
||||||
for (const workspaceFolder of workspaceFolders) {
|
for (const workspaceFolder of workspaceFolders) {
|
||||||
if (workspaceFolder.uri.scheme === "file")
|
if (workspaceFolder.uri.scheme === "file")
|
||||||
diskWorkspaceFolders.push(workspaceFolder.uri.fsPath);
|
diskWorkspaceFolders.push(workspaceFolder);
|
||||||
}
|
}
|
||||||
return diskWorkspaceFolders;
|
return diskWorkspaceFolders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Gets all active workspace folders that are on the filesystem. */
|
||||||
|
export function getOnDiskWorkspaceFolders() {
|
||||||
|
return getOnDiskWorkspaceFoldersObjects().map((folder) => folder.uri.fsPath);
|
||||||
|
}
|
||||||
|
|
||||||
/** Check if folder is already present in workspace */
|
/** Check if folder is already present in workspace */
|
||||||
export function isFolderAlreadyInWorkspace(folderName: string) {
|
export function isFolderAlreadyInWorkspace(folderName: string) {
|
||||||
const workspaceFolders = workspace.workspaceFolders || [];
|
const workspaceFolders = workspace.workspaceFolders || [];
|
||||||
@@ -785,3 +791,39 @@ export async function* walkDirectory(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the path of the first folder in the workspace.
|
||||||
|
* This is used to decide where to create skeleton QL packs.
|
||||||
|
*
|
||||||
|
* If the first folder is a QL pack, then the parent folder is returned.
|
||||||
|
* This is because the vscode-codeql-starter repo contains a ql pack in
|
||||||
|
* the first folder.
|
||||||
|
*
|
||||||
|
* This is a temporary workaround until we can retire the
|
||||||
|
* vscode-codeql-starter repo.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getFirstWorkspaceFolder() {
|
||||||
|
const workspaceFolders = getOnDiskWorkspaceFolders();
|
||||||
|
|
||||||
|
if (!workspaceFolders || workspaceFolders.length === 0) {
|
||||||
|
throw new Error("No workspace folders found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstFolderFsPath = workspaceFolders[0];
|
||||||
|
|
||||||
|
// For the vscode-codeql-starter repo, the first folder will be a ql pack
|
||||||
|
// so we need to get the parent folder
|
||||||
|
if (
|
||||||
|
firstFolderFsPath.includes(
|
||||||
|
join("vscode-codeql-starter", "codeql-custom-queries"),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// return the parent folder
|
||||||
|
return dirname(firstFolderFsPath);
|
||||||
|
} else {
|
||||||
|
// if the first folder is not a ql pack, then we are in a normal workspace
|
||||||
|
return firstFolderFsPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export class ServerProcess implements Disposable {
|
|||||||
dispose(): void {
|
dispose(): void {
|
||||||
void this.logger.log(`Stopping ${this.name}...`);
|
void this.logger.log(`Stopping ${this.name}...`);
|
||||||
this.connection.dispose();
|
this.connection.dispose();
|
||||||
|
this.connection.end();
|
||||||
this.child.stdin!.end();
|
this.child.stdin!.end();
|
||||||
this.child.stderr!.destroy();
|
this.child.stderr!.destroy();
|
||||||
// TODO kill the process if it doesn't terminate after a certain time limit.
|
// TODO kill the process if it doesn't terminate after a certain time limit.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
showAndLogExceptionWithTelemetry,
|
showAndLogExceptionWithTelemetry,
|
||||||
isFolderAlreadyInWorkspace,
|
isFolderAlreadyInWorkspace,
|
||||||
showBinaryChoiceDialog,
|
showBinaryChoiceDialog,
|
||||||
|
getFirstWorkspaceFolder,
|
||||||
} from "./helpers";
|
} from "./helpers";
|
||||||
import { ProgressCallback, withProgress } from "./progress";
|
import { ProgressCallback, withProgress } from "./progress";
|
||||||
import {
|
import {
|
||||||
@@ -29,6 +30,7 @@ import { isCodespacesTemplate } from "./config";
|
|||||||
import { QlPackGenerator } from "./qlpack-generator";
|
import { QlPackGenerator } from "./qlpack-generator";
|
||||||
import { QueryLanguage } from "./common/query-language";
|
import { QueryLanguage } from "./common/query-language";
|
||||||
import { App } from "./common/app";
|
import { App } from "./common/app";
|
||||||
|
import { existsSync } from "fs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* databases.ts
|
* databases.ts
|
||||||
@@ -662,8 +664,13 @@ export class DatabaseManager extends DisposableObject {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const firstWorkspaceFolder = getFirstWorkspaceFolder();
|
||||||
const folderName = `codeql-custom-queries-${databaseItem.language}`;
|
const folderName = `codeql-custom-queries-${databaseItem.language}`;
|
||||||
if (isFolderAlreadyInWorkspace(folderName)) {
|
|
||||||
|
if (
|
||||||
|
existsSync(join(firstWorkspaceFolder, folderName)) ||
|
||||||
|
isFolderAlreadyInWorkspace(folderName)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,7 +687,7 @@ export class DatabaseManager extends DisposableObject {
|
|||||||
folderName,
|
folderName,
|
||||||
databaseItem.language as QueryLanguage,
|
databaseItem.language as QueryLanguage,
|
||||||
this.cli,
|
this.cli,
|
||||||
this.ctx.storageUri?.fsPath,
|
firstWorkspaceFolder,
|
||||||
);
|
);
|
||||||
await qlPackGenerator.generate();
|
await qlPackGenerator.generate();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
@@ -1022,7 +1029,19 @@ export class DatabaseManager extends DisposableObject {
|
|||||||
token: vscode.CancellationToken,
|
token: vscode.CancellationToken,
|
||||||
dbItem: DatabaseItem,
|
dbItem: DatabaseItem,
|
||||||
) {
|
) {
|
||||||
await this.qs.deregisterDatabase(progress, token, dbItem);
|
try {
|
||||||
|
await this.qs.deregisterDatabase(progress, token, dbItem);
|
||||||
|
} catch (e) {
|
||||||
|
const message = getErrorMessage(e);
|
||||||
|
if (message === "Connection is disposed.") {
|
||||||
|
// This is expected if the query server is not running.
|
||||||
|
void extLogger.log(
|
||||||
|
`Could not de-register database '${dbItem.name}' because query server is not running.`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private async registerDatabase(
|
private async registerDatabase(
|
||||||
progress: ProgressCallback,
|
progress: ProgressCallback,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { writeFile } from "fs-extra";
|
import { mkdir, writeFile } from "fs-extra";
|
||||||
import { dump } from "js-yaml";
|
import { dump } from "js-yaml";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { Uri, workspace } from "vscode";
|
import { Uri } from "vscode";
|
||||||
import { CodeQLCliServer } from "./cli";
|
import { CodeQLCliServer } from "./cli";
|
||||||
import { QueryLanguage } from "./common/query-language";
|
import { QueryLanguage } from "./common/query-language";
|
||||||
|
|
||||||
@@ -44,14 +44,7 @@ export class QlPackGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async createWorkspaceFolder() {
|
private async createWorkspaceFolder() {
|
||||||
await workspace.fs.createDirectory(this.folderUri);
|
await mkdir(this.folderUri.fsPath);
|
||||||
|
|
||||||
const end = (workspace.workspaceFolders || []).length;
|
|
||||||
|
|
||||||
workspace.updateWorkspaceFolders(end, 0, {
|
|
||||||
name: this.folderName,
|
|
||||||
uri: this.folderUri,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createQlPackYaml() {
|
private async createQlPackYaml() {
|
||||||
|
|||||||
@@ -1,34 +1,15 @@
|
|||||||
import { assertNever } from "../../pure/helpers-pure";
|
import { assertNever } from "../../pure/helpers-pure";
|
||||||
import {
|
|
||||||
LocalQueryInfo,
|
|
||||||
InitialQueryInfo,
|
|
||||||
CompletedQueryInfo,
|
|
||||||
} from "../../query-results";
|
|
||||||
import { QueryEvaluationInfo } from "../../run-queries-shared";
|
|
||||||
import { QueryHistoryInfo } from "../query-history-info";
|
import { QueryHistoryInfo } from "../query-history-info";
|
||||||
import {
|
import { mapLocalQueryInfoToDto } from "./query-history-local-query-domain-mapper";
|
||||||
QueryHistoryLocalQueryDto,
|
|
||||||
InitialQueryInfoDto,
|
|
||||||
QueryEvaluationInfoDto,
|
|
||||||
CompletedQueryInfoDto,
|
|
||||||
SortedResultSetInfoDto,
|
|
||||||
SortDirectionDto,
|
|
||||||
} from "./query-history-local-query-dto";
|
|
||||||
import { QueryHistoryItemDto } from "./query-history-dto";
|
import { QueryHistoryItemDto } from "./query-history-dto";
|
||||||
import { QueryHistoryVariantAnalysisDto } from "./query-history-variant-analysis-dto";
|
import { mapQueryHistoryVariantAnalysisToDto } from "./query-history-variant-analysis-domain-mapper";
|
||||||
import {
|
|
||||||
RawResultsSortState,
|
|
||||||
SortDirection,
|
|
||||||
SortedResultSetInfo,
|
|
||||||
} from "../../pure/interface-types";
|
|
||||||
|
|
||||||
export function mapQueryHistoryToDto(
|
export function mapQueryHistoryToDto(
|
||||||
queries: QueryHistoryInfo[],
|
queries: QueryHistoryInfo[],
|
||||||
): QueryHistoryItemDto[] {
|
): QueryHistoryItemDto[] {
|
||||||
return queries.map((q) => {
|
return queries.map((q) => {
|
||||||
if (q.t === "variant-analysis") {
|
if (q.t === "variant-analysis") {
|
||||||
const query: QueryHistoryVariantAnalysisDto = q;
|
return mapQueryHistoryVariantAnalysisToDto(q);
|
||||||
return query;
|
|
||||||
} else if (q.t === "local") {
|
} else if (q.t === "local") {
|
||||||
return mapLocalQueryInfoToDto(q);
|
return mapLocalQueryInfoToDto(q);
|
||||||
} else {
|
} else {
|
||||||
@@ -36,105 +17,3 @@ export function mapQueryHistoryToDto(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapLocalQueryInfoToDto(
|
|
||||||
query: LocalQueryInfo,
|
|
||||||
): QueryHistoryLocalQueryDto {
|
|
||||||
return {
|
|
||||||
initialInfo: mapInitialQueryInfoToDto(query.initialInfo),
|
|
||||||
t: "local",
|
|
||||||
evalLogLocation: query.evalLogLocation,
|
|
||||||
evalLogSummaryLocation: query.evalLogSummaryLocation,
|
|
||||||
jsonEvalLogSummaryLocation: query.jsonEvalLogSummaryLocation,
|
|
||||||
evalLogSummarySymbolsLocation: query.evalLogSummarySymbolsLocation,
|
|
||||||
failureReason: query.failureReason,
|
|
||||||
completedQuery:
|
|
||||||
query.completedQuery && mapCompletedQueryToDto(query.completedQuery),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapCompletedQueryToDto(
|
|
||||||
query: CompletedQueryInfo,
|
|
||||||
): CompletedQueryInfoDto {
|
|
||||||
const sortedResults = Object.fromEntries(
|
|
||||||
Object.entries(query.sortedResultsInfo).map(([key, value]) => {
|
|
||||||
return [key, mapSortedResultSetInfoToDto(value)];
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
query: mapQueryEvaluationInfoToDto(query.query),
|
|
||||||
result: {
|
|
||||||
runId: query.result.runId,
|
|
||||||
queryId: query.result.queryId,
|
|
||||||
resultType: query.result.resultType,
|
|
||||||
evaluationTime: query.result.evaluationTime,
|
|
||||||
message: query.result.message,
|
|
||||||
logFileLocation: query.result.logFileLocation,
|
|
||||||
},
|
|
||||||
logFileLocation: query.logFileLocation,
|
|
||||||
successful: query.successful,
|
|
||||||
message: query.message,
|
|
||||||
resultCount: query.resultCount,
|
|
||||||
sortedResultsInfo: sortedResults,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapSortDirectionToDto(sortDirection: SortDirection): SortDirectionDto {
|
|
||||||
switch (sortDirection) {
|
|
||||||
case SortDirection.asc:
|
|
||||||
return SortDirectionDto.asc;
|
|
||||||
case SortDirection.desc:
|
|
||||||
return SortDirectionDto.desc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapRawResultsSortStateToDto(
|
|
||||||
sortState: RawResultsSortState,
|
|
||||||
): SortedResultSetInfoDto["sortState"] {
|
|
||||||
return {
|
|
||||||
columnIndex: sortState.columnIndex,
|
|
||||||
sortDirection: mapSortDirectionToDto(sortState.sortDirection),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapSortedResultSetInfoToDto(
|
|
||||||
resultSet: SortedResultSetInfo,
|
|
||||||
): SortedResultSetInfoDto {
|
|
||||||
return {
|
|
||||||
resultsPath: resultSet.resultsPath,
|
|
||||||
sortState: mapRawResultsSortStateToDto(resultSet.sortState),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapInitialQueryInfoToDto(
|
|
||||||
localQueryInitialInfo: InitialQueryInfo,
|
|
||||||
): InitialQueryInfoDto {
|
|
||||||
return {
|
|
||||||
userSpecifiedLabel: localQueryInitialInfo.userSpecifiedLabel,
|
|
||||||
queryText: localQueryInitialInfo.queryText,
|
|
||||||
isQuickQuery: localQueryInitialInfo.isQuickQuery,
|
|
||||||
isQuickEval: localQueryInitialInfo.isQuickEval,
|
|
||||||
quickEvalPosition: localQueryInitialInfo.quickEvalPosition,
|
|
||||||
queryPath: localQueryInitialInfo.queryPath,
|
|
||||||
databaseInfo: {
|
|
||||||
databaseUri: localQueryInitialInfo.databaseInfo.databaseUri,
|
|
||||||
name: localQueryInitialInfo.databaseInfo.name,
|
|
||||||
},
|
|
||||||
start: localQueryInitialInfo.start,
|
|
||||||
id: localQueryInitialInfo.id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapQueryEvaluationInfoToDto(
|
|
||||||
queryEvaluationInfo: QueryEvaluationInfo,
|
|
||||||
): QueryEvaluationInfoDto {
|
|
||||||
return {
|
|
||||||
querySaveDir: queryEvaluationInfo.querySaveDir,
|
|
||||||
dbItemPath: queryEvaluationInfo.dbItemPath,
|
|
||||||
databaseHasMetadataFile: queryEvaluationInfo.databaseHasMetadataFile,
|
|
||||||
quickEvalPosition: queryEvaluationInfo.quickEvalPosition,
|
|
||||||
metadata: queryEvaluationInfo.metadata,
|
|
||||||
resultsPaths: queryEvaluationInfo.resultsPaths,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,36 +1,14 @@
|
|||||||
import {
|
|
||||||
LocalQueryInfo,
|
|
||||||
CompletedQueryInfo,
|
|
||||||
InitialQueryInfo,
|
|
||||||
} from "../../query-results";
|
|
||||||
import { QueryEvaluationInfo } from "../../run-queries-shared";
|
|
||||||
import { QueryHistoryInfo } from "../query-history-info";
|
import { QueryHistoryInfo } from "../query-history-info";
|
||||||
import { VariantAnalysisHistoryItem } from "../variant-analysis-history-item";
|
|
||||||
import {
|
|
||||||
CompletedQueryInfoDto,
|
|
||||||
QueryEvaluationInfoDto,
|
|
||||||
InitialQueryInfoDto,
|
|
||||||
QueryHistoryLocalQueryDto,
|
|
||||||
SortDirectionDto,
|
|
||||||
InterpretedResultsSortStateDto,
|
|
||||||
SortedResultSetInfoDto,
|
|
||||||
RawResultsSortStateDto,
|
|
||||||
} from "./query-history-local-query-dto";
|
|
||||||
import { QueryHistoryItemDto } from "./query-history-dto";
|
import { QueryHistoryItemDto } from "./query-history-dto";
|
||||||
import {
|
import { mapQueryHistoryVariantAnalysisToDomainModel } from "./query-history-variant-analysis-dto-mapper";
|
||||||
InterpretedResultsSortState,
|
import { mapLocalQueryItemToDomainModel } from "./query-history-local-query-dto-mapper";
|
||||||
RawResultsSortState,
|
|
||||||
SortDirection,
|
|
||||||
SortedResultSetInfo,
|
|
||||||
} from "../../pure/interface-types";
|
|
||||||
|
|
||||||
export function mapQueryHistoryToDomainModel(
|
export function mapQueryHistoryToDomainModel(
|
||||||
queries: QueryHistoryItemDto[],
|
queries: QueryHistoryItemDto[],
|
||||||
): QueryHistoryInfo[] {
|
): QueryHistoryInfo[] {
|
||||||
return queries.map((d) => {
|
return queries.map((d) => {
|
||||||
if (d.t === "variant-analysis") {
|
if (d.t === "variant-analysis") {
|
||||||
const query: VariantAnalysisHistoryItem = d;
|
return mapQueryHistoryVariantAnalysisToDomainModel(d);
|
||||||
return query;
|
|
||||||
} else if (d.t === "local") {
|
} else if (d.t === "local") {
|
||||||
return mapLocalQueryItemToDomainModel(d);
|
return mapLocalQueryItemToDomainModel(d);
|
||||||
}
|
}
|
||||||
@@ -42,122 +20,3 @@ export function mapQueryHistoryToDomainModel(
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapLocalQueryItemToDomainModel(
|
|
||||||
localQuery: QueryHistoryLocalQueryDto,
|
|
||||||
): LocalQueryInfo {
|
|
||||||
return new LocalQueryInfo(
|
|
||||||
mapInitialQueryInfoToDomainModel(localQuery.initialInfo),
|
|
||||||
undefined,
|
|
||||||
localQuery.failureReason,
|
|
||||||
localQuery.completedQuery &&
|
|
||||||
mapCompletedQueryInfoToDomainModel(localQuery.completedQuery),
|
|
||||||
localQuery.evalLogLocation,
|
|
||||||
localQuery.evalLogSummaryLocation,
|
|
||||||
localQuery.jsonEvalLogSummaryLocation,
|
|
||||||
localQuery.evalLogSummarySymbolsLocation,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapCompletedQueryInfoToDomainModel(
|
|
||||||
completedQuery: CompletedQueryInfoDto,
|
|
||||||
): CompletedQueryInfo {
|
|
||||||
const sortState =
|
|
||||||
completedQuery.interpretedResultsSortState &&
|
|
||||||
mapSortStateToDomainModel(completedQuery.interpretedResultsSortState);
|
|
||||||
|
|
||||||
const sortedResults = Object.fromEntries(
|
|
||||||
Object.entries(completedQuery.sortedResultsInfo).map(([key, value]) => {
|
|
||||||
return [key, mapSortedResultSetInfoToDomainModel(value)];
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return new CompletedQueryInfo(
|
|
||||||
mapQueryEvaluationInfoToDomainModel(completedQuery.query),
|
|
||||||
{
|
|
||||||
runId: completedQuery.result.runId,
|
|
||||||
queryId: completedQuery.result.queryId,
|
|
||||||
resultType: completedQuery.result.resultType,
|
|
||||||
evaluationTime: completedQuery.result.evaluationTime,
|
|
||||||
message: completedQuery.result.message,
|
|
||||||
logFileLocation: completedQuery.result.logFileLocation,
|
|
||||||
},
|
|
||||||
completedQuery.logFileLocation,
|
|
||||||
completedQuery.successful ?? completedQuery.sucessful,
|
|
||||||
completedQuery.message,
|
|
||||||
sortState,
|
|
||||||
completedQuery.resultCount,
|
|
||||||
sortedResults,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapInitialQueryInfoToDomainModel(
|
|
||||||
initialInfo: InitialQueryInfoDto,
|
|
||||||
): InitialQueryInfo {
|
|
||||||
return {
|
|
||||||
userSpecifiedLabel: initialInfo.userSpecifiedLabel,
|
|
||||||
queryText: initialInfo.queryText,
|
|
||||||
isQuickQuery: initialInfo.isQuickQuery,
|
|
||||||
isQuickEval: initialInfo.isQuickEval,
|
|
||||||
quickEvalPosition: initialInfo.quickEvalPosition,
|
|
||||||
queryPath: initialInfo.queryPath,
|
|
||||||
databaseInfo: {
|
|
||||||
databaseUri: initialInfo.databaseInfo.databaseUri,
|
|
||||||
name: initialInfo.databaseInfo.name,
|
|
||||||
},
|
|
||||||
start: new Date(initialInfo.start),
|
|
||||||
id: initialInfo.id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapQueryEvaluationInfoToDomainModel(
|
|
||||||
evaluationInfo: QueryEvaluationInfoDto,
|
|
||||||
): QueryEvaluationInfo {
|
|
||||||
return new QueryEvaluationInfo(
|
|
||||||
evaluationInfo.querySaveDir,
|
|
||||||
evaluationInfo.dbItemPath,
|
|
||||||
evaluationInfo.databaseHasMetadataFile,
|
|
||||||
evaluationInfo.quickEvalPosition,
|
|
||||||
evaluationInfo.metadata,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapSortDirectionToDomainModel(
|
|
||||||
sortDirection: SortDirectionDto,
|
|
||||||
): SortDirection {
|
|
||||||
switch (sortDirection) {
|
|
||||||
case SortDirectionDto.asc:
|
|
||||||
return SortDirection.asc;
|
|
||||||
case SortDirectionDto.desc:
|
|
||||||
return SortDirection.desc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapSortStateToDomainModel(
|
|
||||||
sortState: InterpretedResultsSortStateDto,
|
|
||||||
): InterpretedResultsSortState {
|
|
||||||
return {
|
|
||||||
sortBy: sortState.sortBy,
|
|
||||||
sortDirection: mapSortDirectionToDomainModel(sortState.sortDirection),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapSortedResultSetInfoToDomainModel(
|
|
||||||
sortedResultSetInfo: SortedResultSetInfoDto,
|
|
||||||
): SortedResultSetInfo {
|
|
||||||
return {
|
|
||||||
resultsPath: sortedResultSetInfo.resultsPath,
|
|
||||||
sortState: mapRawResultsSortStateToDomainModel(
|
|
||||||
sortedResultSetInfo.sortState,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapRawResultsSortStateToDomainModel(
|
|
||||||
sortState: RawResultsSortStateDto,
|
|
||||||
): RawResultsSortState {
|
|
||||||
return {
|
|
||||||
columnIndex: sortState.columnIndex,
|
|
||||||
sortDirection: mapSortDirectionToDomainModel(sortState.sortDirection),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import {
|
||||||
|
LocalQueryInfo,
|
||||||
|
InitialQueryInfo,
|
||||||
|
CompletedQueryInfo,
|
||||||
|
} from "../../query-results";
|
||||||
|
import { QueryEvaluationInfo } from "../../run-queries-shared";
|
||||||
|
import {
|
||||||
|
QueryHistoryLocalQueryDto,
|
||||||
|
InitialQueryInfoDto,
|
||||||
|
QueryEvaluationInfoDto,
|
||||||
|
CompletedQueryInfoDto,
|
||||||
|
SortedResultSetInfoDto,
|
||||||
|
SortDirectionDto,
|
||||||
|
} from "./query-history-local-query-dto";
|
||||||
|
import {
|
||||||
|
RawResultsSortState,
|
||||||
|
SortDirection,
|
||||||
|
SortedResultSetInfo,
|
||||||
|
} from "../../pure/interface-types";
|
||||||
|
|
||||||
|
export function mapLocalQueryInfoToDto(
|
||||||
|
query: LocalQueryInfo,
|
||||||
|
): QueryHistoryLocalQueryDto {
|
||||||
|
return {
|
||||||
|
initialInfo: mapInitialQueryInfoToDto(query.initialInfo),
|
||||||
|
t: "local",
|
||||||
|
evalLogLocation: query.evalLogLocation,
|
||||||
|
evalLogSummaryLocation: query.evalLogSummaryLocation,
|
||||||
|
jsonEvalLogSummaryLocation: query.jsonEvalLogSummaryLocation,
|
||||||
|
evalLogSummarySymbolsLocation: query.evalLogSummarySymbolsLocation,
|
||||||
|
failureReason: query.failureReason,
|
||||||
|
completedQuery:
|
||||||
|
query.completedQuery && mapCompletedQueryToDto(query.completedQuery),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapCompletedQueryToDto(
|
||||||
|
query: CompletedQueryInfo,
|
||||||
|
): CompletedQueryInfoDto {
|
||||||
|
const sortedResults = Object.fromEntries(
|
||||||
|
Object.entries(query.sortedResultsInfo).map(([key, value]) => {
|
||||||
|
return [key, mapSortedResultSetInfoToDto(value)];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
query: mapQueryEvaluationInfoToDto(query.query),
|
||||||
|
result: {
|
||||||
|
runId: query.result.runId,
|
||||||
|
queryId: query.result.queryId,
|
||||||
|
resultType: query.result.resultType,
|
||||||
|
evaluationTime: query.result.evaluationTime,
|
||||||
|
message: query.result.message,
|
||||||
|
logFileLocation: query.result.logFileLocation,
|
||||||
|
},
|
||||||
|
logFileLocation: query.logFileLocation,
|
||||||
|
successful: query.successful,
|
||||||
|
message: query.message,
|
||||||
|
resultCount: query.resultCount,
|
||||||
|
sortedResultsInfo: sortedResults,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapSortDirectionToDto(sortDirection: SortDirection): SortDirectionDto {
|
||||||
|
switch (sortDirection) {
|
||||||
|
case SortDirection.asc:
|
||||||
|
return SortDirectionDto.asc;
|
||||||
|
case SortDirection.desc:
|
||||||
|
return SortDirectionDto.desc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapRawResultsSortStateToDto(
|
||||||
|
sortState: RawResultsSortState,
|
||||||
|
): SortedResultSetInfoDto["sortState"] {
|
||||||
|
return {
|
||||||
|
columnIndex: sortState.columnIndex,
|
||||||
|
sortDirection: mapSortDirectionToDto(sortState.sortDirection),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapSortedResultSetInfoToDto(
|
||||||
|
resultSet: SortedResultSetInfo,
|
||||||
|
): SortedResultSetInfoDto {
|
||||||
|
return {
|
||||||
|
resultsPath: resultSet.resultsPath,
|
||||||
|
sortState: mapRawResultsSortStateToDto(resultSet.sortState),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapInitialQueryInfoToDto(
|
||||||
|
localQueryInitialInfo: InitialQueryInfo,
|
||||||
|
): InitialQueryInfoDto {
|
||||||
|
return {
|
||||||
|
userSpecifiedLabel: localQueryInitialInfo.userSpecifiedLabel,
|
||||||
|
queryText: localQueryInitialInfo.queryText,
|
||||||
|
isQuickQuery: localQueryInitialInfo.isQuickQuery,
|
||||||
|
isQuickEval: localQueryInitialInfo.isQuickEval,
|
||||||
|
quickEvalPosition: localQueryInitialInfo.quickEvalPosition,
|
||||||
|
queryPath: localQueryInitialInfo.queryPath,
|
||||||
|
databaseInfo: {
|
||||||
|
databaseUri: localQueryInitialInfo.databaseInfo.databaseUri,
|
||||||
|
name: localQueryInitialInfo.databaseInfo.name,
|
||||||
|
},
|
||||||
|
start: localQueryInitialInfo.start,
|
||||||
|
id: localQueryInitialInfo.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapQueryEvaluationInfoToDto(
|
||||||
|
queryEvaluationInfo: QueryEvaluationInfo,
|
||||||
|
): QueryEvaluationInfoDto {
|
||||||
|
return {
|
||||||
|
querySaveDir: queryEvaluationInfo.querySaveDir,
|
||||||
|
dbItemPath: queryEvaluationInfo.dbItemPath,
|
||||||
|
databaseHasMetadataFile: queryEvaluationInfo.databaseHasMetadataFile,
|
||||||
|
quickEvalPosition: queryEvaluationInfo.quickEvalPosition,
|
||||||
|
metadata: queryEvaluationInfo.metadata,
|
||||||
|
resultsPaths: queryEvaluationInfo.resultsPaths,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import {
|
||||||
|
LocalQueryInfo,
|
||||||
|
CompletedQueryInfo,
|
||||||
|
InitialQueryInfo,
|
||||||
|
} from "../../query-results";
|
||||||
|
import { QueryEvaluationInfo } from "../../run-queries-shared";
|
||||||
|
import {
|
||||||
|
CompletedQueryInfoDto,
|
||||||
|
QueryEvaluationInfoDto,
|
||||||
|
InitialQueryInfoDto,
|
||||||
|
QueryHistoryLocalQueryDto,
|
||||||
|
SortDirectionDto,
|
||||||
|
InterpretedResultsSortStateDto,
|
||||||
|
SortedResultSetInfoDto,
|
||||||
|
RawResultsSortStateDto,
|
||||||
|
} from "./query-history-local-query-dto";
|
||||||
|
import {
|
||||||
|
InterpretedResultsSortState,
|
||||||
|
RawResultsSortState,
|
||||||
|
SortDirection,
|
||||||
|
SortedResultSetInfo,
|
||||||
|
} from "../../pure/interface-types";
|
||||||
|
|
||||||
|
export function mapLocalQueryItemToDomainModel(
|
||||||
|
localQuery: QueryHistoryLocalQueryDto,
|
||||||
|
): LocalQueryInfo {
|
||||||
|
return new LocalQueryInfo(
|
||||||
|
mapInitialQueryInfoToDomainModel(localQuery.initialInfo),
|
||||||
|
undefined,
|
||||||
|
localQuery.failureReason,
|
||||||
|
localQuery.completedQuery &&
|
||||||
|
mapCompletedQueryInfoToDomainModel(localQuery.completedQuery),
|
||||||
|
localQuery.evalLogLocation,
|
||||||
|
localQuery.evalLogSummaryLocation,
|
||||||
|
localQuery.jsonEvalLogSummaryLocation,
|
||||||
|
localQuery.evalLogSummarySymbolsLocation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapCompletedQueryInfoToDomainModel(
|
||||||
|
completedQuery: CompletedQueryInfoDto,
|
||||||
|
): CompletedQueryInfo {
|
||||||
|
const sortState =
|
||||||
|
completedQuery.interpretedResultsSortState &&
|
||||||
|
mapSortStateToDomainModel(completedQuery.interpretedResultsSortState);
|
||||||
|
|
||||||
|
const sortedResults = Object.fromEntries(
|
||||||
|
Object.entries(completedQuery.sortedResultsInfo).map(([key, value]) => {
|
||||||
|
return [key, mapSortedResultSetInfoToDomainModel(value)];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return new CompletedQueryInfo(
|
||||||
|
mapQueryEvaluationInfoToDomainModel(completedQuery.query),
|
||||||
|
{
|
||||||
|
runId: completedQuery.result.runId,
|
||||||
|
queryId: completedQuery.result.queryId,
|
||||||
|
resultType: completedQuery.result.resultType,
|
||||||
|
evaluationTime: completedQuery.result.evaluationTime,
|
||||||
|
message: completedQuery.result.message,
|
||||||
|
logFileLocation: completedQuery.result.logFileLocation,
|
||||||
|
},
|
||||||
|
completedQuery.logFileLocation,
|
||||||
|
completedQuery.successful ?? completedQuery.sucessful,
|
||||||
|
completedQuery.message,
|
||||||
|
sortState,
|
||||||
|
completedQuery.resultCount,
|
||||||
|
sortedResults,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapInitialQueryInfoToDomainModel(
|
||||||
|
initialInfo: InitialQueryInfoDto,
|
||||||
|
): InitialQueryInfo {
|
||||||
|
return {
|
||||||
|
userSpecifiedLabel: initialInfo.userSpecifiedLabel,
|
||||||
|
queryText: initialInfo.queryText,
|
||||||
|
isQuickQuery: initialInfo.isQuickQuery,
|
||||||
|
isQuickEval: initialInfo.isQuickEval,
|
||||||
|
quickEvalPosition: initialInfo.quickEvalPosition,
|
||||||
|
queryPath: initialInfo.queryPath,
|
||||||
|
databaseInfo: {
|
||||||
|
databaseUri: initialInfo.databaseInfo.databaseUri,
|
||||||
|
name: initialInfo.databaseInfo.name,
|
||||||
|
},
|
||||||
|
start: new Date(initialInfo.start),
|
||||||
|
id: initialInfo.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapQueryEvaluationInfoToDomainModel(
|
||||||
|
evaluationInfo: QueryEvaluationInfoDto,
|
||||||
|
): QueryEvaluationInfo {
|
||||||
|
return new QueryEvaluationInfo(
|
||||||
|
evaluationInfo.querySaveDir,
|
||||||
|
evaluationInfo.dbItemPath,
|
||||||
|
evaluationInfo.databaseHasMetadataFile,
|
||||||
|
evaluationInfo.quickEvalPosition,
|
||||||
|
evaluationInfo.metadata,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapSortDirectionToDomainModel(
|
||||||
|
sortDirection: SortDirectionDto,
|
||||||
|
): SortDirection {
|
||||||
|
switch (sortDirection) {
|
||||||
|
case SortDirectionDto.asc:
|
||||||
|
return SortDirection.asc;
|
||||||
|
case SortDirectionDto.desc:
|
||||||
|
return SortDirection.desc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapSortStateToDomainModel(
|
||||||
|
sortState: InterpretedResultsSortStateDto,
|
||||||
|
): InterpretedResultsSortState {
|
||||||
|
return {
|
||||||
|
sortBy: sortState.sortBy,
|
||||||
|
sortDirection: mapSortDirectionToDomainModel(sortState.sortDirection),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapSortedResultSetInfoToDomainModel(
|
||||||
|
sortedResultSetInfo: SortedResultSetInfoDto,
|
||||||
|
): SortedResultSetInfo {
|
||||||
|
return {
|
||||||
|
resultsPath: sortedResultSetInfo.resultsPath,
|
||||||
|
sortState: mapRawResultsSortStateToDomainModel(
|
||||||
|
sortedResultSetInfo.sortState,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapRawResultsSortStateToDomainModel(
|
||||||
|
sortState: RawResultsSortStateDto,
|
||||||
|
): RawResultsSortState {
|
||||||
|
return {
|
||||||
|
columnIndex: sortState.columnIndex,
|
||||||
|
sortDirection: mapSortDirectionToDomainModel(sortState.sortDirection),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
import {
|
||||||
|
QueryHistoryVariantAnalysisDto,
|
||||||
|
QueryLanguageDto,
|
||||||
|
QueryStatusDto,
|
||||||
|
VariantAnalysisDto,
|
||||||
|
VariantAnalysisFailureReasonDto,
|
||||||
|
VariantAnalysisRepoStatusDto,
|
||||||
|
VariantAnalysisScannedRepositoryDto,
|
||||||
|
VariantAnalysisSkippedRepositoriesDto,
|
||||||
|
VariantAnalysisSkippedRepositoryDto,
|
||||||
|
VariantAnalysisSkippedRepositoryGroupDto,
|
||||||
|
VariantAnalysisStatusDto,
|
||||||
|
} from "./query-history-variant-analysis-dto";
|
||||||
|
import {
|
||||||
|
VariantAnalysis,
|
||||||
|
VariantAnalysisFailureReason,
|
||||||
|
VariantAnalysisRepoStatus,
|
||||||
|
VariantAnalysisScannedRepository,
|
||||||
|
VariantAnalysisSkippedRepositories,
|
||||||
|
VariantAnalysisSkippedRepository,
|
||||||
|
VariantAnalysisSkippedRepositoryGroup,
|
||||||
|
VariantAnalysisStatus,
|
||||||
|
} from "../../variant-analysis/shared/variant-analysis";
|
||||||
|
import { assertNever } from "../../pure/helpers-pure";
|
||||||
|
import { QueryLanguage } from "../../common/query-language";
|
||||||
|
import { QueryStatus } from "../../query-status";
|
||||||
|
import { VariantAnalysisHistoryItem } from "../variant-analysis-history-item";
|
||||||
|
|
||||||
|
export function mapQueryHistoryVariantAnalysisToDto(
|
||||||
|
item: VariantAnalysisHistoryItem,
|
||||||
|
): QueryHistoryVariantAnalysisDto {
|
||||||
|
return {
|
||||||
|
t: "variant-analysis",
|
||||||
|
failureReason: item.failureReason,
|
||||||
|
resultCount: item.resultCount,
|
||||||
|
status: mapQueryStatusToDto(item.status),
|
||||||
|
completed: item.completed,
|
||||||
|
variantAnalysis: mapVariantAnalysisDtoToDto(item.variantAnalysis),
|
||||||
|
userSpecifiedLabel: item.userSpecifiedLabel,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisDtoToDto(
|
||||||
|
variantAnalysis: VariantAnalysis,
|
||||||
|
): VariantAnalysisDto {
|
||||||
|
return {
|
||||||
|
id: variantAnalysis.id,
|
||||||
|
controllerRepo: {
|
||||||
|
id: variantAnalysis.controllerRepo.id,
|
||||||
|
fullName: variantAnalysis.controllerRepo.fullName,
|
||||||
|
private: variantAnalysis.controllerRepo.private,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
name: variantAnalysis.query.name,
|
||||||
|
filePath: variantAnalysis.query.filePath,
|
||||||
|
language: mapQueryLanguageToDto(variantAnalysis.query.language),
|
||||||
|
text: variantAnalysis.query.text,
|
||||||
|
},
|
||||||
|
databases: {
|
||||||
|
repositories: variantAnalysis.databases.repositories,
|
||||||
|
repositoryLists: variantAnalysis.databases.repositoryLists,
|
||||||
|
repositoryOwners: variantAnalysis.databases.repositoryOwners,
|
||||||
|
},
|
||||||
|
createdAt: variantAnalysis.createdAt,
|
||||||
|
updatedAt: variantAnalysis.updatedAt,
|
||||||
|
executionStartTime: variantAnalysis.executionStartTime,
|
||||||
|
status: mapVariantAnalysisStatusToDto(variantAnalysis.status),
|
||||||
|
completedAt: variantAnalysis.completedAt,
|
||||||
|
actionsWorkflowRunId: variantAnalysis.actionsWorkflowRunId,
|
||||||
|
failureReason:
|
||||||
|
variantAnalysis.failureReason &&
|
||||||
|
mapVariantAnalysisFailureReasonToDto(variantAnalysis.failureReason),
|
||||||
|
scannedRepos:
|
||||||
|
variantAnalysis.scannedRepos &&
|
||||||
|
mapVariantAnalysisScannedRepositoriesToDto(variantAnalysis.scannedRepos),
|
||||||
|
skippedRepos:
|
||||||
|
variantAnalysis.skippedRepos &&
|
||||||
|
mapVariantAnalysisSkippedRepositoriesToDto(variantAnalysis.skippedRepos),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisScannedRepositoriesToDto(
|
||||||
|
repos: VariantAnalysisScannedRepository[],
|
||||||
|
): VariantAnalysisScannedRepositoryDto[] {
|
||||||
|
return repos.map(mapVariantAnalysisScannedRepositoryToDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisScannedRepositoryToDto(
|
||||||
|
repo: VariantAnalysisScannedRepository,
|
||||||
|
): VariantAnalysisScannedRepositoryDto {
|
||||||
|
return {
|
||||||
|
repository: {
|
||||||
|
id: repo.repository.id,
|
||||||
|
fullName: repo.repository.fullName,
|
||||||
|
private: repo.repository.private,
|
||||||
|
stargazersCount: repo.repository.stargazersCount,
|
||||||
|
updatedAt: repo.repository.updatedAt,
|
||||||
|
},
|
||||||
|
analysisStatus: mapVariantAnalysisRepoStatusToDto(repo.analysisStatus),
|
||||||
|
resultCount: repo.resultCount,
|
||||||
|
artifactSizeInBytes: repo.artifactSizeInBytes,
|
||||||
|
failureMessage: repo.failureMessage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisSkippedRepositoriesToDto(
|
||||||
|
repos: VariantAnalysisSkippedRepositories,
|
||||||
|
): VariantAnalysisSkippedRepositoriesDto {
|
||||||
|
return {
|
||||||
|
accessMismatchRepos:
|
||||||
|
repos.accessMismatchRepos &&
|
||||||
|
mapVariantAnalysisSkippedRepositoryGroupToDto(repos.accessMismatchRepos),
|
||||||
|
notFoundRepos:
|
||||||
|
repos.notFoundRepos &&
|
||||||
|
mapVariantAnalysisSkippedRepositoryGroupToDto(repos.notFoundRepos),
|
||||||
|
noCodeqlDbRepos:
|
||||||
|
repos.noCodeqlDbRepos &&
|
||||||
|
mapVariantAnalysisSkippedRepositoryGroupToDto(repos.noCodeqlDbRepos),
|
||||||
|
overLimitRepos:
|
||||||
|
repos.overLimitRepos &&
|
||||||
|
mapVariantAnalysisSkippedRepositoryGroupToDto(repos.overLimitRepos),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisSkippedRepositoryGroupToDto(
|
||||||
|
repoGroup: VariantAnalysisSkippedRepositoryGroup,
|
||||||
|
): VariantAnalysisSkippedRepositoryGroupDto {
|
||||||
|
return {
|
||||||
|
repositoryCount: repoGroup.repositoryCount,
|
||||||
|
repositories: repoGroup.repositories.map(
|
||||||
|
mapVariantAnalysisSkippedRepositoryToDto,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisSkippedRepositoryToDto(
|
||||||
|
repo: VariantAnalysisSkippedRepository,
|
||||||
|
): VariantAnalysisSkippedRepositoryDto {
|
||||||
|
return {
|
||||||
|
id: repo.id,
|
||||||
|
fullName: repo.fullName,
|
||||||
|
private: repo.private,
|
||||||
|
stargazersCount: repo.stargazersCount,
|
||||||
|
updatedAt: repo.updatedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisFailureReasonToDto(
|
||||||
|
failureReason: VariantAnalysisFailureReason,
|
||||||
|
): VariantAnalysisFailureReasonDto {
|
||||||
|
switch (failureReason) {
|
||||||
|
case VariantAnalysisFailureReason.NoReposQueried:
|
||||||
|
return VariantAnalysisFailureReasonDto.NoReposQueried;
|
||||||
|
case VariantAnalysisFailureReason.ActionsWorkflowRunFailed:
|
||||||
|
return VariantAnalysisFailureReasonDto.ActionsWorkflowRunFailed;
|
||||||
|
case VariantAnalysisFailureReason.InternalError:
|
||||||
|
return VariantAnalysisFailureReasonDto.InternalError;
|
||||||
|
default:
|
||||||
|
assertNever(failureReason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisRepoStatusToDto(
|
||||||
|
status: VariantAnalysisRepoStatus,
|
||||||
|
): VariantAnalysisRepoStatusDto {
|
||||||
|
switch (status) {
|
||||||
|
case VariantAnalysisRepoStatus.Pending:
|
||||||
|
return VariantAnalysisRepoStatusDto.Pending;
|
||||||
|
case VariantAnalysisRepoStatus.InProgress:
|
||||||
|
return VariantAnalysisRepoStatusDto.InProgress;
|
||||||
|
case VariantAnalysisRepoStatus.Succeeded:
|
||||||
|
return VariantAnalysisRepoStatusDto.Succeeded;
|
||||||
|
case VariantAnalysisRepoStatus.Failed:
|
||||||
|
return VariantAnalysisRepoStatusDto.Failed;
|
||||||
|
case VariantAnalysisRepoStatus.Canceled:
|
||||||
|
return VariantAnalysisRepoStatusDto.Canceled;
|
||||||
|
case VariantAnalysisRepoStatus.TimedOut:
|
||||||
|
return VariantAnalysisRepoStatusDto.TimedOut;
|
||||||
|
default:
|
||||||
|
assertNever(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisStatusToDto(
|
||||||
|
status: VariantAnalysisStatus,
|
||||||
|
): VariantAnalysisStatusDto {
|
||||||
|
switch (status) {
|
||||||
|
case VariantAnalysisStatus.InProgress:
|
||||||
|
return VariantAnalysisStatusDto.InProgress;
|
||||||
|
case VariantAnalysisStatus.Succeeded:
|
||||||
|
return VariantAnalysisStatusDto.Succeeded;
|
||||||
|
case VariantAnalysisStatus.Failed:
|
||||||
|
return VariantAnalysisStatusDto.Failed;
|
||||||
|
case VariantAnalysisStatus.Canceled:
|
||||||
|
return VariantAnalysisStatusDto.Canceled;
|
||||||
|
default:
|
||||||
|
assertNever(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapQueryLanguageToDto(language: QueryLanguage): QueryLanguageDto {
|
||||||
|
switch (language) {
|
||||||
|
case QueryLanguage.CSharp:
|
||||||
|
return QueryLanguageDto.CSharp;
|
||||||
|
case QueryLanguage.Cpp:
|
||||||
|
return QueryLanguageDto.Cpp;
|
||||||
|
case QueryLanguage.Go:
|
||||||
|
return QueryLanguageDto.Go;
|
||||||
|
case QueryLanguage.Java:
|
||||||
|
return QueryLanguageDto.Java;
|
||||||
|
case QueryLanguage.Javascript:
|
||||||
|
return QueryLanguageDto.Javascript;
|
||||||
|
case QueryLanguage.Python:
|
||||||
|
return QueryLanguageDto.Python;
|
||||||
|
case QueryLanguage.Ruby:
|
||||||
|
return QueryLanguageDto.Ruby;
|
||||||
|
case QueryLanguage.Swift:
|
||||||
|
return QueryLanguageDto.Swift;
|
||||||
|
default:
|
||||||
|
assertNever(language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapQueryStatusToDto(status: QueryStatus): QueryStatusDto {
|
||||||
|
switch (status) {
|
||||||
|
case QueryStatus.InProgress:
|
||||||
|
return QueryStatusDto.InProgress;
|
||||||
|
case QueryStatus.Completed:
|
||||||
|
return QueryStatusDto.Completed;
|
||||||
|
case QueryStatus.Failed:
|
||||||
|
return QueryStatusDto.Failed;
|
||||||
|
default:
|
||||||
|
assertNever(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
import {
|
||||||
|
QueryHistoryVariantAnalysisDto,
|
||||||
|
QueryLanguageDto,
|
||||||
|
QueryStatusDto,
|
||||||
|
VariantAnalysisDto,
|
||||||
|
VariantAnalysisFailureReasonDto,
|
||||||
|
VariantAnalysisRepoStatusDto,
|
||||||
|
VariantAnalysisScannedRepositoryDto,
|
||||||
|
VariantAnalysisSkippedRepositoriesDto,
|
||||||
|
VariantAnalysisSkippedRepositoryDto,
|
||||||
|
VariantAnalysisSkippedRepositoryGroupDto,
|
||||||
|
VariantAnalysisStatusDto,
|
||||||
|
} from "./query-history-variant-analysis-dto";
|
||||||
|
import {
|
||||||
|
VariantAnalysis,
|
||||||
|
VariantAnalysisFailureReason,
|
||||||
|
VariantAnalysisRepoStatus,
|
||||||
|
VariantAnalysisScannedRepository,
|
||||||
|
VariantAnalysisSkippedRepositories,
|
||||||
|
VariantAnalysisSkippedRepository,
|
||||||
|
VariantAnalysisSkippedRepositoryGroup,
|
||||||
|
VariantAnalysisStatus,
|
||||||
|
} from "../../variant-analysis/shared/variant-analysis";
|
||||||
|
import { assertNever } from "../../pure/helpers-pure";
|
||||||
|
import { QueryLanguage } from "../../common/query-language";
|
||||||
|
import { QueryStatus } from "../../query-status";
|
||||||
|
import { VariantAnalysisHistoryItem } from "../variant-analysis-history-item";
|
||||||
|
|
||||||
|
export function mapQueryHistoryVariantAnalysisToDomainModel(
|
||||||
|
item: QueryHistoryVariantAnalysisDto,
|
||||||
|
): VariantAnalysisHistoryItem {
|
||||||
|
return {
|
||||||
|
t: "variant-analysis",
|
||||||
|
failureReason: item.failureReason,
|
||||||
|
resultCount: item.resultCount,
|
||||||
|
status: mapQueryStatusToDomainModel(item.status),
|
||||||
|
completed: item.completed,
|
||||||
|
variantAnalysis: mapVariantAnalysisToDomainModel(item.variantAnalysis),
|
||||||
|
userSpecifiedLabel: item.userSpecifiedLabel,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisToDomainModel(
|
||||||
|
variantAnalysis: VariantAnalysisDto,
|
||||||
|
): VariantAnalysis {
|
||||||
|
return {
|
||||||
|
id: variantAnalysis.id,
|
||||||
|
controllerRepo: {
|
||||||
|
id: variantAnalysis.controllerRepo.id,
|
||||||
|
fullName: variantAnalysis.controllerRepo.fullName,
|
||||||
|
private: variantAnalysis.controllerRepo.private,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
name: variantAnalysis.query.name,
|
||||||
|
filePath: variantAnalysis.query.filePath,
|
||||||
|
language: mapQueryLanguageToDomainModel(variantAnalysis.query.language),
|
||||||
|
text: variantAnalysis.query.text,
|
||||||
|
},
|
||||||
|
databases: {
|
||||||
|
repositories: variantAnalysis.databases.repositories,
|
||||||
|
repositoryLists: variantAnalysis.databases.repositoryLists,
|
||||||
|
repositoryOwners: variantAnalysis.databases.repositoryOwners,
|
||||||
|
},
|
||||||
|
createdAt: variantAnalysis.createdAt,
|
||||||
|
updatedAt: variantAnalysis.updatedAt,
|
||||||
|
executionStartTime: variantAnalysis.executionStartTime,
|
||||||
|
status: mapVariantAnalysisStatusToDomainModel(variantAnalysis.status),
|
||||||
|
completedAt: variantAnalysis.completedAt,
|
||||||
|
actionsWorkflowRunId: variantAnalysis.actionsWorkflowRunId,
|
||||||
|
failureReason:
|
||||||
|
variantAnalysis.failureReason &&
|
||||||
|
mapVariantAnalysisFailureReasonToDomainModel(
|
||||||
|
variantAnalysis.failureReason,
|
||||||
|
),
|
||||||
|
scannedRepos:
|
||||||
|
variantAnalysis.scannedRepos &&
|
||||||
|
mapVariantAnalysisScannedRepositoriesToDomainModel(
|
||||||
|
variantAnalysis.scannedRepos,
|
||||||
|
),
|
||||||
|
skippedRepos:
|
||||||
|
variantAnalysis.skippedRepos &&
|
||||||
|
mapVariantAnalysisSkippedRepositoriesToDomainModel(
|
||||||
|
variantAnalysis.skippedRepos,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisScannedRepositoriesToDomainModel(
|
||||||
|
repos: VariantAnalysisScannedRepositoryDto[],
|
||||||
|
): VariantAnalysisScannedRepository[] {
|
||||||
|
return repos.map(mapVariantAnalysisScannedRepositoryToDomainModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisScannedRepositoryToDomainModel(
|
||||||
|
repo: VariantAnalysisScannedRepositoryDto,
|
||||||
|
): VariantAnalysisScannedRepository {
|
||||||
|
return {
|
||||||
|
repository: {
|
||||||
|
id: repo.repository.id,
|
||||||
|
fullName: repo.repository.fullName,
|
||||||
|
private: repo.repository.private,
|
||||||
|
stargazersCount: repo.repository.stargazersCount,
|
||||||
|
updatedAt: repo.repository.updatedAt,
|
||||||
|
},
|
||||||
|
analysisStatus: mapVariantAnalysisRepoStatusToDomainModel(
|
||||||
|
repo.analysisStatus,
|
||||||
|
),
|
||||||
|
resultCount: repo.resultCount,
|
||||||
|
artifactSizeInBytes: repo.artifactSizeInBytes,
|
||||||
|
failureMessage: repo.failureMessage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisSkippedRepositoriesToDomainModel(
|
||||||
|
repos: VariantAnalysisSkippedRepositoriesDto,
|
||||||
|
): VariantAnalysisSkippedRepositories {
|
||||||
|
return {
|
||||||
|
accessMismatchRepos:
|
||||||
|
repos.accessMismatchRepos &&
|
||||||
|
mapVariantAnalysisSkippedRepositoryGroupToDomainModel(
|
||||||
|
repos.accessMismatchRepos,
|
||||||
|
),
|
||||||
|
notFoundRepos:
|
||||||
|
repos.notFoundRepos &&
|
||||||
|
mapVariantAnalysisSkippedRepositoryGroupToDomainModel(
|
||||||
|
repos.notFoundRepos,
|
||||||
|
),
|
||||||
|
noCodeqlDbRepos:
|
||||||
|
repos.noCodeqlDbRepos &&
|
||||||
|
mapVariantAnalysisSkippedRepositoryGroupToDomainModel(
|
||||||
|
repos.noCodeqlDbRepos,
|
||||||
|
),
|
||||||
|
overLimitRepos:
|
||||||
|
repos.overLimitRepos &&
|
||||||
|
mapVariantAnalysisSkippedRepositoryGroupToDomainModel(
|
||||||
|
repos.overLimitRepos,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisSkippedRepositoryGroupToDomainModel(
|
||||||
|
repoGroup: VariantAnalysisSkippedRepositoryGroupDto,
|
||||||
|
): VariantAnalysisSkippedRepositoryGroup {
|
||||||
|
return {
|
||||||
|
repositoryCount: repoGroup.repositoryCount,
|
||||||
|
repositories: repoGroup.repositories.map(
|
||||||
|
mapVariantAnalysisSkippedRepositoryToDomainModel,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisSkippedRepositoryToDomainModel(
|
||||||
|
repo: VariantAnalysisSkippedRepositoryDto,
|
||||||
|
): VariantAnalysisSkippedRepository {
|
||||||
|
return {
|
||||||
|
id: repo.id,
|
||||||
|
fullName: repo.fullName,
|
||||||
|
private: repo.private,
|
||||||
|
stargazersCount: repo.stargazersCount,
|
||||||
|
updatedAt: repo.updatedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisFailureReasonToDomainModel(
|
||||||
|
failureReason: VariantAnalysisFailureReasonDto,
|
||||||
|
): VariantAnalysisFailureReason {
|
||||||
|
switch (failureReason) {
|
||||||
|
case VariantAnalysisFailureReasonDto.NoReposQueried:
|
||||||
|
return VariantAnalysisFailureReason.NoReposQueried;
|
||||||
|
case VariantAnalysisFailureReasonDto.ActionsWorkflowRunFailed:
|
||||||
|
return VariantAnalysisFailureReason.ActionsWorkflowRunFailed;
|
||||||
|
case VariantAnalysisFailureReasonDto.InternalError:
|
||||||
|
return VariantAnalysisFailureReason.InternalError;
|
||||||
|
default:
|
||||||
|
assertNever(failureReason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisRepoStatusToDomainModel(
|
||||||
|
status: VariantAnalysisRepoStatusDto,
|
||||||
|
): VariantAnalysisRepoStatus {
|
||||||
|
switch (status) {
|
||||||
|
case VariantAnalysisRepoStatusDto.Pending:
|
||||||
|
return VariantAnalysisRepoStatus.Pending;
|
||||||
|
case VariantAnalysisRepoStatusDto.InProgress:
|
||||||
|
return VariantAnalysisRepoStatus.InProgress;
|
||||||
|
case VariantAnalysisRepoStatusDto.Succeeded:
|
||||||
|
return VariantAnalysisRepoStatus.Succeeded;
|
||||||
|
case VariantAnalysisRepoStatusDto.Failed:
|
||||||
|
return VariantAnalysisRepoStatus.Failed;
|
||||||
|
case VariantAnalysisRepoStatusDto.Canceled:
|
||||||
|
return VariantAnalysisRepoStatus.Canceled;
|
||||||
|
case VariantAnalysisRepoStatusDto.TimedOut:
|
||||||
|
return VariantAnalysisRepoStatus.TimedOut;
|
||||||
|
default:
|
||||||
|
assertNever(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapVariantAnalysisStatusToDomainModel(
|
||||||
|
status: VariantAnalysisStatusDto,
|
||||||
|
): VariantAnalysisStatus {
|
||||||
|
switch (status) {
|
||||||
|
case VariantAnalysisStatusDto.InProgress:
|
||||||
|
return VariantAnalysisStatus.InProgress;
|
||||||
|
case VariantAnalysisStatusDto.Succeeded:
|
||||||
|
return VariantAnalysisStatus.Succeeded;
|
||||||
|
case VariantAnalysisStatusDto.Failed:
|
||||||
|
return VariantAnalysisStatus.Failed;
|
||||||
|
case VariantAnalysisStatusDto.Canceled:
|
||||||
|
return VariantAnalysisStatus.Canceled;
|
||||||
|
default:
|
||||||
|
assertNever(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapQueryLanguageToDomainModel(
|
||||||
|
language: QueryLanguageDto,
|
||||||
|
): QueryLanguage {
|
||||||
|
switch (language) {
|
||||||
|
case QueryLanguageDto.CSharp:
|
||||||
|
return QueryLanguage.CSharp;
|
||||||
|
case QueryLanguageDto.Cpp:
|
||||||
|
return QueryLanguage.Cpp;
|
||||||
|
case QueryLanguageDto.Go:
|
||||||
|
return QueryLanguage.Go;
|
||||||
|
case QueryLanguageDto.Java:
|
||||||
|
return QueryLanguage.Java;
|
||||||
|
case QueryLanguageDto.Javascript:
|
||||||
|
return QueryLanguage.Javascript;
|
||||||
|
case QueryLanguageDto.Python:
|
||||||
|
return QueryLanguage.Python;
|
||||||
|
case QueryLanguageDto.Ruby:
|
||||||
|
return QueryLanguage.Ruby;
|
||||||
|
case QueryLanguageDto.Swift:
|
||||||
|
return QueryLanguage.Swift;
|
||||||
|
default:
|
||||||
|
assertNever(language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapQueryStatusToDomainModel(status: QueryStatusDto): QueryStatus {
|
||||||
|
switch (status) {
|
||||||
|
case QueryStatusDto.InProgress:
|
||||||
|
return QueryStatus.InProgress;
|
||||||
|
case QueryStatusDto.Completed:
|
||||||
|
return QueryStatus.Completed;
|
||||||
|
case QueryStatusDto.Failed:
|
||||||
|
return QueryStatus.Failed;
|
||||||
|
default:
|
||||||
|
assertNever(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +1,17 @@
|
|||||||
// Contains models and consts for the data we want to store in the query history store.
|
// Contains models and consts for the data we want to store in the query history store.
|
||||||
// Changes to these models should be done carefully and account for backwards compatibility of data.
|
// Changes to these models should be done carefully and account for backwards compatibility of data.
|
||||||
|
|
||||||
import { QueryLanguage } from "../../common/query-language";
|
|
||||||
import { QueryStatus } from "../../query-status";
|
|
||||||
import {
|
|
||||||
VariantAnalysisFailureReason,
|
|
||||||
VariantAnalysisRepoStatus,
|
|
||||||
VariantAnalysisStatus,
|
|
||||||
} from "../../variant-analysis/shared/variant-analysis";
|
|
||||||
|
|
||||||
// All data points are modelled, except enums.
|
|
||||||
|
|
||||||
export interface QueryHistoryVariantAnalysisDto {
|
export interface QueryHistoryVariantAnalysisDto {
|
||||||
readonly t: "variant-analysis";
|
readonly t: "variant-analysis";
|
||||||
failureReason?: string;
|
failureReason?: string;
|
||||||
resultCount?: number;
|
resultCount?: number;
|
||||||
status: QueryStatus;
|
status: QueryStatusDto;
|
||||||
completed: boolean;
|
completed: boolean;
|
||||||
variantAnalysis: VariantAnalysisQueryHistoryDto;
|
variantAnalysis: VariantAnalysisDto;
|
||||||
userSpecifiedLabel?: string;
|
userSpecifiedLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VariantAnalysisQueryHistoryDto {
|
export interface VariantAnalysisDto {
|
||||||
id: number;
|
id: number;
|
||||||
controllerRepo: {
|
controllerRepo: {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -31,7 +21,7 @@ export interface VariantAnalysisQueryHistoryDto {
|
|||||||
query: {
|
query: {
|
||||||
name: string;
|
name: string;
|
||||||
filePath: string;
|
filePath: string;
|
||||||
language: QueryLanguage;
|
language: QueryLanguageDto;
|
||||||
text: string;
|
text: string;
|
||||||
};
|
};
|
||||||
databases: {
|
databases: {
|
||||||
@@ -42,10 +32,10 @@ export interface VariantAnalysisQueryHistoryDto {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
executionStartTime: number;
|
executionStartTime: number;
|
||||||
status: VariantAnalysisStatus;
|
status: VariantAnalysisStatusDto;
|
||||||
completedAt?: string;
|
completedAt?: string;
|
||||||
actionsWorkflowRunId?: number;
|
actionsWorkflowRunId?: number;
|
||||||
failureReason?: VariantAnalysisFailureReason;
|
failureReason?: VariantAnalysisFailureReasonDto;
|
||||||
scannedRepos?: VariantAnalysisScannedRepositoryDto[];
|
scannedRepos?: VariantAnalysisScannedRepositoryDto[];
|
||||||
skippedRepos?: VariantAnalysisSkippedRepositoriesDto;
|
skippedRepos?: VariantAnalysisSkippedRepositoriesDto;
|
||||||
}
|
}
|
||||||
@@ -58,7 +48,7 @@ export interface VariantAnalysisScannedRepositoryDto {
|
|||||||
stargazersCount: number;
|
stargazersCount: number;
|
||||||
updatedAt: string | null;
|
updatedAt: string | null;
|
||||||
};
|
};
|
||||||
analysisStatus: VariantAnalysisRepoStatus;
|
analysisStatus: VariantAnalysisRepoStatusDto;
|
||||||
resultCount?: number;
|
resultCount?: number;
|
||||||
artifactSizeInBytes?: number;
|
artifactSizeInBytes?: number;
|
||||||
failureMessage?: string;
|
failureMessage?: string;
|
||||||
@@ -83,3 +73,42 @@ export interface VariantAnalysisSkippedRepositoryDto {
|
|||||||
stargazersCount?: number;
|
stargazersCount?: number;
|
||||||
updatedAt?: string | null;
|
updatedAt?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum VariantAnalysisFailureReasonDto {
|
||||||
|
NoReposQueried = "noReposQueried",
|
||||||
|
ActionsWorkflowRunFailed = "actionsWorkflowRunFailed",
|
||||||
|
InternalError = "internalError",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum VariantAnalysisRepoStatusDto {
|
||||||
|
Pending = "pending",
|
||||||
|
InProgress = "inProgress",
|
||||||
|
Succeeded = "succeeded",
|
||||||
|
Failed = "failed",
|
||||||
|
Canceled = "canceled",
|
||||||
|
TimedOut = "timedOut",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum VariantAnalysisStatusDto {
|
||||||
|
InProgress = "inProgress",
|
||||||
|
Succeeded = "succeeded",
|
||||||
|
Failed = "failed",
|
||||||
|
Canceled = "canceled",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum QueryLanguageDto {
|
||||||
|
CSharp = "csharp",
|
||||||
|
Cpp = "cpp",
|
||||||
|
Go = "go",
|
||||||
|
Java = "java",
|
||||||
|
Javascript = "javascript",
|
||||||
|
Python = "python",
|
||||||
|
Ruby = "ruby",
|
||||||
|
Swift = "swift",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum QueryStatusDto {
|
||||||
|
InProgress = "InProgress",
|
||||||
|
Completed = "Completed",
|
||||||
|
Failed = "Failed",
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ensureFile } from "fs-extra";
|
import { ensureFile } from "fs-extra";
|
||||||
|
|
||||||
import { DisposableObject } from "../pure/disposable-object";
|
import { DisposableObject, DisposeHandler } from "../pure/disposable-object";
|
||||||
import { CancellationToken } from "vscode";
|
import { CancellationToken } from "vscode";
|
||||||
import { createMessageConnection, RequestType } from "vscode-jsonrpc/node";
|
import { createMessageConnection, RequestType } from "vscode-jsonrpc/node";
|
||||||
import * as cli from "../cli";
|
import * as cli from "../cli";
|
||||||
@@ -224,4 +224,10 @@ export class QueryServerClient extends DisposableObject {
|
|||||||
delete this.progressCallbacks[id];
|
delete this.progressCallbacks[id];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public dispose(disposeHandler?: DisposeHandler | undefined): void {
|
||||||
|
this.progressCallbacks = {};
|
||||||
|
this.stopQueryServer();
|
||||||
|
super.dispose(disposeHandler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import { join, dirname } from "path";
|
import { join } from "path";
|
||||||
import { CancellationToken, Uri, workspace, window as Window } from "vscode";
|
import { CancellationToken, Uri, workspace, window as Window } from "vscode";
|
||||||
import { CodeQLCliServer } from "./cli";
|
import { CodeQLCliServer } from "./cli";
|
||||||
import { OutputChannelLogger } from "./common";
|
import { OutputChannelLogger } from "./common";
|
||||||
import { Credentials } from "./common/authentication";
|
import { Credentials } from "./common/authentication";
|
||||||
import { QueryLanguage } from "./common/query-language";
|
import { QueryLanguage } from "./common/query-language";
|
||||||
import { askForLanguage, isFolderAlreadyInWorkspace } from "./helpers";
|
import {
|
||||||
|
askForLanguage,
|
||||||
|
getFirstWorkspaceFolder,
|
||||||
|
isFolderAlreadyInWorkspace,
|
||||||
|
} from "./helpers";
|
||||||
import { getErrorMessage } from "./pure/helpers-pure";
|
import { getErrorMessage } from "./pure/helpers-pure";
|
||||||
import { QlPackGenerator } from "./qlpack-generator";
|
import { QlPackGenerator } from "./qlpack-generator";
|
||||||
import { DatabaseItem, DatabaseManager } from "./local-databases";
|
import { DatabaseItem, DatabaseManager } from "./local-databases";
|
||||||
import { ProgressCallback, UserCancellationException } from "./progress";
|
import { ProgressCallback, UserCancellationException } from "./progress";
|
||||||
import { askForGitHubRepo, downloadGitHubDatabase } from "./databaseFetcher";
|
import { askForGitHubRepo, downloadGitHubDatabase } from "./databaseFetcher";
|
||||||
|
import { existsSync } from "fs";
|
||||||
|
|
||||||
type QueryLanguagesToDatabaseMap = Record<string, string>;
|
type QueryLanguagesToDatabaseMap = Record<string, string>;
|
||||||
|
|
||||||
@@ -50,11 +55,11 @@ export class SkeletonQueryWizard {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.qlPackStoragePath = this.getFirstStoragePath();
|
this.qlPackStoragePath = getFirstWorkspaceFolder();
|
||||||
|
|
||||||
const skeletonPackAlreadyExists = isFolderAlreadyInWorkspace(
|
const skeletonPackAlreadyExists =
|
||||||
this.folderName,
|
existsSync(join(this.qlPackStoragePath, this.folderName)) ||
|
||||||
);
|
isFolderAlreadyInWorkspace(this.folderName);
|
||||||
|
|
||||||
if (skeletonPackAlreadyExists) {
|
if (skeletonPackAlreadyExists) {
|
||||||
// just create a new example query file in skeleton QL pack
|
// just create a new example query file in skeleton QL pack
|
||||||
@@ -93,27 +98,6 @@ export class SkeletonQueryWizard {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getFirstStoragePath() {
|
|
||||||
const workspaceFolders = workspace.workspaceFolders;
|
|
||||||
|
|
||||||
if (!workspaceFolders || workspaceFolders.length === 0) {
|
|
||||||
throw new Error("No workspace folders found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstFolder = workspaceFolders[0];
|
|
||||||
const firstFolderFsPath = firstFolder.uri.fsPath;
|
|
||||||
|
|
||||||
// For the vscode-codeql-starter repo, the first folder will be a ql pack
|
|
||||||
// so we need to get the parent folder
|
|
||||||
if (firstFolderFsPath.includes("codeql-custom-queries")) {
|
|
||||||
// return the parent folder
|
|
||||||
return dirname(firstFolderFsPath);
|
|
||||||
} else {
|
|
||||||
// if the first folder is not a ql pack, then we are in a normal workspace
|
|
||||||
return firstFolderFsPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async chooseLanguage() {
|
private async chooseLanguage() {
|
||||||
this.progress({
|
this.progress({
|
||||||
message: "Choose language",
|
message: "Choose language",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { ModeledMethod } from "../../data-extensions-editor/modeled-method";
|
|||||||
import { MethodRow } from "./MethodRow";
|
import { MethodRow } from "./MethodRow";
|
||||||
import { assertNever } from "../../pure/helpers-pure";
|
import { assertNever } from "../../pure/helpers-pure";
|
||||||
import { vscode } from "../vscode-api";
|
import { vscode } from "../vscode-api";
|
||||||
import { calculateSupportedPercentage } from "./supported";
|
import { calculateModeledPercentage } from "./modeled";
|
||||||
|
|
||||||
export const DataExtensionsEditorContainer = styled.div`
|
export const DataExtensionsEditorContainer = styled.div`
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
@@ -97,12 +97,12 @@ export function DataExtensionsEditor({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const supportedPercentage = useMemo(
|
const modeledPercentage = useMemo(
|
||||||
() => calculateSupportedPercentage(externalApiUsages),
|
() => calculateModeledPercentage(externalApiUsages),
|
||||||
[externalApiUsages],
|
[externalApiUsages],
|
||||||
);
|
);
|
||||||
|
|
||||||
const unsupportedPercentage = 100 - supportedPercentage;
|
const unModeledPercentage = 100 - modeledPercentage;
|
||||||
|
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(method: ExternalApiUsage, model: ModeledMethod) => {
|
(method: ExternalApiUsage, model: ModeledMethod) => {
|
||||||
@@ -140,10 +140,10 @@ export function DataExtensionsEditor({
|
|||||||
{externalApiUsages.length > 0 && (
|
{externalApiUsages.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<h3>External API support stats</h3>
|
<h3>External API model stats</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Supported: {supportedPercentage.toFixed(2)}%</li>
|
<li>Modeled: {modeledPercentage.toFixed(2)}%</li>
|
||||||
<li>Unsupported: {unsupportedPercentage.toFixed(2)}%</li>
|
<li>Unmodeled: {unModeledPercentage.toFixed(2)}%</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { useCallback, useEffect } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react";
|
||||||
|
|
||||||
|
import type { ModeledMethod } from "../../data-extensions-editor/modeled-method";
|
||||||
|
|
||||||
|
const Dropdown = styled(VSCodeDropdown)`
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
kinds: Array<ModeledMethod["kind"]>;
|
||||||
|
|
||||||
|
value: ModeledMethod["kind"] | undefined;
|
||||||
|
onChange: (value: ModeledMethod["kind"]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const KindInput = ({ kinds, value, onChange }: Props) => {
|
||||||
|
const handleInput = useCallback(
|
||||||
|
(e: InputEvent) => {
|
||||||
|
const target = e.target as HTMLSelectElement;
|
||||||
|
|
||||||
|
onChange(target.value as ModeledMethod["kind"]);
|
||||||
|
},
|
||||||
|
[onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (value === undefined && kinds.length > 0) {
|
||||||
|
onChange(kinds[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value !== undefined && !kinds.includes(value)) {
|
||||||
|
onChange(kinds[0]);
|
||||||
|
}
|
||||||
|
}, [value, kinds, onChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown value={value} onInput={handleInput}>
|
||||||
|
{kinds.map((kind) => (
|
||||||
|
<VSCodeOption key={kind} value={kind}>
|
||||||
|
{kind}
|
||||||
|
</VSCodeOption>
|
||||||
|
))}
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
VSCodeDataGridRow,
|
VSCodeDataGridRow,
|
||||||
VSCodeDropdown,
|
VSCodeDropdown,
|
||||||
VSCodeOption,
|
VSCodeOption,
|
||||||
VSCodeTextField,
|
|
||||||
} from "@vscode/webview-ui-toolkit/react";
|
} from "@vscode/webview-ui-toolkit/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
@@ -15,15 +14,13 @@ import {
|
|||||||
ModeledMethod,
|
ModeledMethod,
|
||||||
ModeledMethodType,
|
ModeledMethodType,
|
||||||
} from "../../data-extensions-editor/modeled-method";
|
} from "../../data-extensions-editor/modeled-method";
|
||||||
|
import { KindInput } from "./KindInput";
|
||||||
|
import { extensiblePredicateDefinitions } from "../../data-extensions-editor/predicates";
|
||||||
|
|
||||||
const Dropdown = styled(VSCodeDropdown)`
|
const Dropdown = styled(VSCodeDropdown)`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const TextField = styled(VSCodeTextField)`
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
type SupportedUnsupportedSpanProps = {
|
type SupportedUnsupportedSpanProps = {
|
||||||
supported: boolean;
|
supported: boolean;
|
||||||
};
|
};
|
||||||
@@ -107,17 +104,15 @@ export const MethodRow = ({
|
|||||||
},
|
},
|
||||||
[onChange, externalApiUsage, modeledMethod],
|
[onChange, externalApiUsage, modeledMethod],
|
||||||
);
|
);
|
||||||
const handleKindInput = useCallback(
|
const handleKindChange = useCallback(
|
||||||
(e: InputEvent) => {
|
(kind: string) => {
|
||||||
if (!modeledMethod) {
|
if (!modeledMethod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const target = e.target as HTMLSelectElement;
|
|
||||||
|
|
||||||
onChange(externalApiUsage, {
|
onChange(externalApiUsage, {
|
||||||
...modeledMethod,
|
...modeledMethod,
|
||||||
kind: target.value as ModeledMethod["kind"],
|
kind,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[onChange, externalApiUsage, modeledMethod],
|
[onChange, externalApiUsage, modeledMethod],
|
||||||
@@ -130,6 +125,11 @@ export const MethodRow = ({
|
|||||||
});
|
});
|
||||||
}, [externalApiUsage]);
|
}, [externalApiUsage]);
|
||||||
|
|
||||||
|
const predicate =
|
||||||
|
modeledMethod?.type && modeledMethod.type !== "none"
|
||||||
|
? extensiblePredicateDefinitions[modeledMethod.type]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VSCodeDataGridRow>
|
<VSCodeDataGridRow>
|
||||||
<VSCodeDataGridCell gridColumn={1}>
|
<VSCodeDataGridCell gridColumn={1}>
|
||||||
@@ -155,7 +155,7 @@ export const MethodRow = ({
|
|||||||
value={modeledMethod?.type ?? "none"}
|
value={modeledMethod?.type ?? "none"}
|
||||||
onInput={handleTypeInput}
|
onInput={handleTypeInput}
|
||||||
>
|
>
|
||||||
<VSCodeOption value="none">Unmodelled</VSCodeOption>
|
<VSCodeOption value="none">Unmodeled</VSCodeOption>
|
||||||
<VSCodeOption value="source">Source</VSCodeOption>
|
<VSCodeOption value="source">Source</VSCodeOption>
|
||||||
<VSCodeOption value="sink">Sink</VSCodeOption>
|
<VSCodeOption value="sink">Sink</VSCodeOption>
|
||||||
<VSCodeOption value="summary">Flow summary</VSCodeOption>
|
<VSCodeOption value="summary">Flow summary</VSCodeOption>
|
||||||
@@ -195,10 +195,13 @@ export const MethodRow = ({
|
|||||||
)}
|
)}
|
||||||
</VSCodeDataGridCell>
|
</VSCodeDataGridCell>
|
||||||
<VSCodeDataGridCell gridColumn={7}>
|
<VSCodeDataGridCell gridColumn={7}>
|
||||||
{modeledMethod?.type &&
|
{predicate?.supportedKinds && (
|
||||||
["source", "sink", "summary"].includes(modeledMethod?.type) && (
|
<KindInput
|
||||||
<TextField value={modeledMethod?.kind} onInput={handleKindInput} />
|
kinds={predicate.supportedKinds}
|
||||||
)}
|
value={modeledMethod?.kind}
|
||||||
|
onChange={handleKindChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</VSCodeDataGridCell>
|
</VSCodeDataGridCell>
|
||||||
</VSCodeDataGridRow>
|
</VSCodeDataGridRow>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { calculateSupportedPercentage } from "../supported";
|
import { calculateModeledPercentage } from "../modeled";
|
||||||
|
|
||||||
describe("calculateSupportedPercentage", () => {
|
describe("calculateModeledPercentage", () => {
|
||||||
it("when there are no external API usages", () => {
|
it("when there are no external API usages", () => {
|
||||||
expect(calculateSupportedPercentage([])).toBe(0);
|
expect(calculateModeledPercentage([])).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when there are is 1 supported external API usage", () => {
|
it("when there are is 1 modeled external API usage", () => {
|
||||||
expect(
|
expect(
|
||||||
calculateSupportedPercentage([
|
calculateModeledPercentage([
|
||||||
{
|
{
|
||||||
supported: true,
|
supported: true,
|
||||||
},
|
},
|
||||||
@@ -15,9 +15,9 @@ describe("calculateSupportedPercentage", () => {
|
|||||||
).toBe(100);
|
).toBe(100);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when there are is 1 unsupported external API usage", () => {
|
it("when there are is 1 unmodeled external API usage", () => {
|
||||||
expect(
|
expect(
|
||||||
calculateSupportedPercentage([
|
calculateModeledPercentage([
|
||||||
{
|
{
|
||||||
supported: false,
|
supported: false,
|
||||||
},
|
},
|
||||||
@@ -25,9 +25,9 @@ describe("calculateSupportedPercentage", () => {
|
|||||||
).toBe(0);
|
).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when there are multiple supporte and unsupported external API usage", () => {
|
it("when there are multiple modeled and unmodeled external API usage", () => {
|
||||||
expect(
|
expect(
|
||||||
calculateSupportedPercentage([
|
calculateModeledPercentage([
|
||||||
{
|
{
|
||||||
supported: false,
|
supported: false,
|
||||||
},
|
},
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
|
||||||
|
|
||||||
|
export function calculateModeledPercentage(
|
||||||
|
externalApiUsages: Array<Pick<ExternalApiUsage, "supported">>,
|
||||||
|
): number {
|
||||||
|
if (externalApiUsages.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modeledExternalApiUsages = externalApiUsages.filter((m) => m.supported);
|
||||||
|
|
||||||
|
const modeledRatio =
|
||||||
|
modeledExternalApiUsages.length / externalApiUsages.length;
|
||||||
|
return modeledRatio * 100;
|
||||||
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
|
|
||||||
|
|
||||||
export function calculateSupportedPercentage(
|
|
||||||
externalApiUsages: Array<Pick<ExternalApiUsage, "supported">>,
|
|
||||||
): number {
|
|
||||||
if (externalApiUsages.length === 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const supportedExternalApiUsages = externalApiUsages.filter(
|
|
||||||
(m) => m.supported,
|
|
||||||
);
|
|
||||||
|
|
||||||
const supportedRatio =
|
|
||||||
supportedExternalApiUsages.length / externalApiUsages.length;
|
|
||||||
return supportedRatio * 100;
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
describe("createDataExtensionYaml", () => {
|
describe("createDataExtensionYaml", () => {
|
||||||
it("creates the correct YAML file", () => {
|
it("creates the correct YAML file", () => {
|
||||||
const yaml = createDataExtensionYaml(
|
const yaml = createDataExtensionYaml(
|
||||||
|
"java",
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
signature: "org.sql2o.Connection#createQuery(String)",
|
signature: "org.sql2o.Connection#createQuery(String)",
|
||||||
@@ -99,6 +100,32 @@ describe("createDataExtensionYaml", () => {
|
|||||||
pack: codeql/java-all
|
pack: codeql/java-all
|
||||||
extensible: neutralModel
|
extensible: neutralModel
|
||||||
data: []
|
data: []
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes the correct language", () => {
|
||||||
|
const yaml = createDataExtensionYaml("csharp", [], {});
|
||||||
|
|
||||||
|
expect(yaml).toEqual(`extensions:
|
||||||
|
- addsTo:
|
||||||
|
pack: codeql/csharp-all
|
||||||
|
extensible: sourceModel
|
||||||
|
data: []
|
||||||
|
|
||||||
|
- addsTo:
|
||||||
|
pack: codeql/csharp-all
|
||||||
|
extensible: sinkModel
|
||||||
|
data: []
|
||||||
|
|
||||||
|
- addsTo:
|
||||||
|
pack: codeql/csharp-all
|
||||||
|
extensible: summaryModel
|
||||||
|
data: []
|
||||||
|
|
||||||
|
- addsTo:
|
||||||
|
pack: codeql/csharp-all
|
||||||
|
extensible: neutralModel
|
||||||
|
data: []
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ describe("Db panel UI commands", () => {
|
|||||||
it.skip("should add new local db list", async () => {
|
it.skip("should add new local db list", async () => {
|
||||||
// Add db list
|
// Add db list
|
||||||
jest.spyOn(window, "showQuickPick").mockResolvedValue({
|
jest.spyOn(window, "showQuickPick").mockResolvedValue({
|
||||||
kind: DbListKind.Local,
|
databaseKind: DbListKind.Local,
|
||||||
} as AddListQuickPickItem);
|
} as AddListQuickPickItem);
|
||||||
jest.spyOn(window, "showInputBox").mockResolvedValue("my-list-1");
|
jest.spyOn(window, "showInputBox").mockResolvedValue("my-list-1");
|
||||||
await commandManager.execute(
|
await commandManager.execute(
|
||||||
@@ -73,7 +73,7 @@ describe("Db panel UI commands", () => {
|
|||||||
it("should add new remote repository", async () => {
|
it("should add new remote repository", async () => {
|
||||||
// Add db
|
// Add db
|
||||||
jest.spyOn(window, "showQuickPick").mockResolvedValue({
|
jest.spyOn(window, "showQuickPick").mockResolvedValue({
|
||||||
kind: "repo",
|
remoteDatabaseKind: "repo",
|
||||||
} as RemoteDatabaseQuickPickItem);
|
} as RemoteDatabaseQuickPickItem);
|
||||||
|
|
||||||
jest.spyOn(window, "showInputBox").mockResolvedValue("owner1/repo1");
|
jest.spyOn(window, "showInputBox").mockResolvedValue("owner1/repo1");
|
||||||
@@ -96,7 +96,7 @@ describe("Db panel UI commands", () => {
|
|||||||
it("should add new remote owner", async () => {
|
it("should add new remote owner", async () => {
|
||||||
// Add owner
|
// Add owner
|
||||||
jest.spyOn(window, "showQuickPick").mockResolvedValue({
|
jest.spyOn(window, "showQuickPick").mockResolvedValue({
|
||||||
kind: "owner",
|
remoteDatabaseKind: "owner",
|
||||||
} as RemoteDatabaseQuickPickItem);
|
} as RemoteDatabaseQuickPickItem);
|
||||||
|
|
||||||
jest.spyOn(window, "showInputBox").mockResolvedValue("owner1");
|
jest.spyOn(window, "showInputBox").mockResolvedValue("owner1");
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ import {
|
|||||||
} from "../global.helper";
|
} from "../global.helper";
|
||||||
import { createMockCommandManager } from "../../__mocks__/commandsMock";
|
import { createMockCommandManager } from "../../__mocks__/commandsMock";
|
||||||
|
|
||||||
jest.setTimeout(60_000);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run various integration tests for databases
|
* Run various integration tests for databases
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ import { CodeQLCliServer } from "../../../src/cli";
|
|||||||
import { tryGetQueryMetadata } from "../../../src/helpers";
|
import { tryGetQueryMetadata } from "../../../src/helpers";
|
||||||
import { getActivatedExtension } from "../global.helper";
|
import { getActivatedExtension } from "../global.helper";
|
||||||
|
|
||||||
// up to 3 minutes per test
|
|
||||||
jest.setTimeout(3 * 60 * 1000);
|
|
||||||
|
|
||||||
describe("helpers (with CLI)", () => {
|
describe("helpers (with CLI)", () => {
|
||||||
const baseDir = __dirname;
|
const baseDir = __dirname;
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ const config: Config = {
|
|||||||
...baseConfig,
|
...baseConfig,
|
||||||
runner: "<rootDir>/../jest-runner-installed-extensions.ts",
|
runner: "<rootDir>/../jest-runner-installed-extensions.ts",
|
||||||
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
|
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
|
||||||
|
// CLI integration tests call into the CLI and execute queries, so these are expected to take a lot longer
|
||||||
|
// than the default 5 seconds.
|
||||||
|
testTimeout: 180_000, // 3 minutes
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
@@ -98,8 +98,6 @@ const db: messages.Dataset = {
|
|||||||
workingSet: "default",
|
workingSet: "default",
|
||||||
};
|
};
|
||||||
|
|
||||||
jest.setTimeout(60_000);
|
|
||||||
|
|
||||||
describeWithCodeQL()("using the legacy query server", () => {
|
describeWithCodeQL()("using the legacy query server", () => {
|
||||||
const nullProgressReporter: ProgressReporter = {
|
const nullProgressReporter: ProgressReporter = {
|
||||||
report: () => {
|
report: () => {
|
||||||
|
|||||||
@@ -104,8 +104,6 @@ const nullProgressReporter: ProgressReporter = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
jest.setTimeout(20_000);
|
|
||||||
|
|
||||||
describeWithCodeQL()("using the new query server", () => {
|
describeWithCodeQL()("using the new query server", () => {
|
||||||
let qs: qsClient.QueryServerClient;
|
let qs: qsClient.QueryServerClient;
|
||||||
let cliServer: cli.CodeQLCliServer;
|
let cliServer: cli.CodeQLCliServer;
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ import {
|
|||||||
import { mockedQuickPickItem } from "../utils/mocking.helpers";
|
import { mockedQuickPickItem } from "../utils/mocking.helpers";
|
||||||
import { getActivatedExtension } from "../global.helper";
|
import { getActivatedExtension } from "../global.helper";
|
||||||
|
|
||||||
// up to 3 minutes per test
|
|
||||||
jest.setTimeout(3 * 60 * 1000);
|
|
||||||
|
|
||||||
describe("Packaging commands", () => {
|
describe("Packaging commands", () => {
|
||||||
let cli: CodeQLCliServer;
|
let cli: CodeQLCliServer;
|
||||||
const progress = jest.fn();
|
const progress = jest.fn();
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ import { QueryResultType } from "../../../src/pure/new-messages";
|
|||||||
import { createVSCodeCommandManager } from "../../../src/common/vscode/commands";
|
import { createVSCodeCommandManager } from "../../../src/common/vscode/commands";
|
||||||
import { AllCommands, QueryServerCommands } from "../../../src/common/commands";
|
import { AllCommands, QueryServerCommands } from "../../../src/common/commands";
|
||||||
|
|
||||||
jest.setTimeout(20_000);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration tests for queries
|
* Integration tests for queries
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import { KeyType } from "../../../src/contextual/keyType";
|
|||||||
import { faker } from "@faker-js/faker";
|
import { faker } from "@faker-js/faker";
|
||||||
import { getActivatedExtension } from "../global.helper";
|
import { getActivatedExtension } from "../global.helper";
|
||||||
|
|
||||||
jest.setTimeout(60_000);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform proper integration tests by running the CLI
|
* Perform proper integration tests by running the CLI
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ import * as databaseFetcher from "../../../src/databaseFetcher";
|
|||||||
import { createMockDB } from "../../factories/databases/databases";
|
import { createMockDB } from "../../factories/databases/databases";
|
||||||
import { asError } from "../../../src/pure/helpers-pure";
|
import { asError } from "../../../src/pure/helpers-pure";
|
||||||
|
|
||||||
jest.setTimeout(80_000);
|
|
||||||
|
|
||||||
describe("SkeletonQueryWizard", () => {
|
describe("SkeletonQueryWizard", () => {
|
||||||
let mockCli: CodeQLCliServer;
|
let mockCli: CodeQLCliServer;
|
||||||
let wizard: SkeletonQueryWizard;
|
let wizard: SkeletonQueryWizard;
|
||||||
@@ -84,11 +82,11 @@ describe("SkeletonQueryWizard", () => {
|
|||||||
jest.spyOn(workspace, "workspaceFolders", "get").mockReturnValue([
|
jest.spyOn(workspace, "workspaceFolders", "get").mockReturnValue([
|
||||||
{
|
{
|
||||||
name: `codespaces-codeql`,
|
name: `codespaces-codeql`,
|
||||||
uri: { fsPath: storagePath },
|
uri: { fsPath: storagePath, scheme: "file" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "/second/folder/path",
|
name: "/second/folder/path",
|
||||||
uri: { fsPath: storagePath },
|
uri: { fsPath: storagePath, scheme: "file" },
|
||||||
},
|
},
|
||||||
] as WorkspaceFolder[]);
|
] as WorkspaceFolder[]);
|
||||||
|
|
||||||
@@ -304,66 +302,6 @@ describe("SkeletonQueryWizard", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getFirstStoragePath", () => {
|
|
||||||
it("should return the first workspace folder", async () => {
|
|
||||||
jest.spyOn(workspace, "workspaceFolders", "get").mockReturnValue([
|
|
||||||
{
|
|
||||||
name: "codespaces-codeql",
|
|
||||||
uri: { fsPath: "codespaces-codeql" },
|
|
||||||
},
|
|
||||||
] as WorkspaceFolder[]);
|
|
||||||
|
|
||||||
wizard = new SkeletonQueryWizard(
|
|
||||||
mockCli,
|
|
||||||
jest.fn(),
|
|
||||||
credentials,
|
|
||||||
extLogger,
|
|
||||||
mockDatabaseManager,
|
|
||||||
token,
|
|
||||||
storagePath,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(wizard.getFirstStoragePath()).toEqual("codespaces-codeql");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("if user is in vscode-codeql-starter workspace", () => {
|
|
||||||
it("should set storage path to parent folder", async () => {
|
|
||||||
jest.spyOn(workspace, "workspaceFolders", "get").mockReturnValue([
|
|
||||||
{
|
|
||||||
name: "codeql-custom-queries-cpp",
|
|
||||||
uri: {
|
|
||||||
fsPath: join(
|
|
||||||
"vscode-codeql-starter",
|
|
||||||
"codeql-custom-queries-cpp",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "codeql-custom-queries-csharp",
|
|
||||||
uri: {
|
|
||||||
fsPath: join(
|
|
||||||
"vscode-codeql-starter",
|
|
||||||
"codeql-custom-queries-csharp",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] as WorkspaceFolder[]);
|
|
||||||
|
|
||||||
wizard = new SkeletonQueryWizard(
|
|
||||||
mockCli,
|
|
||||||
jest.fn(),
|
|
||||||
credentials,
|
|
||||||
extLogger,
|
|
||||||
mockDatabaseManager,
|
|
||||||
token,
|
|
||||||
storagePath,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(wizard.getFirstStoragePath()).toEqual("vscode-codeql-starter");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("findDatabaseItemByNwo", () => {
|
describe("findDatabaseItemByNwo", () => {
|
||||||
describe("when the item exists", () => {
|
describe("when the item exists", () => {
|
||||||
it("should return the database item", async () => {
|
it("should return the database item", async () => {
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import { readFile, writeFile, ensureDir, copy } from "fs-extra";
|
|||||||
import { createVSCodeCommandManager } from "../../../src/common/vscode/commands";
|
import { createVSCodeCommandManager } from "../../../src/common/vscode/commands";
|
||||||
import { AllCommands } from "../../../src/common/commands";
|
import { AllCommands } from "../../../src/common/commands";
|
||||||
|
|
||||||
jest.setTimeout(20_000);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration tests for queries
|
* Integration tests for queries
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -29,9 +29,6 @@ import { QueryLanguage } from "../../../../src/common/query-language";
|
|||||||
import { readBundledPack } from "../../utils/bundled-pack-helpers";
|
import { readBundledPack } from "../../utils/bundled-pack-helpers";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
|
|
||||||
// up to 3 minutes per test
|
|
||||||
jest.setTimeout(3 * 60 * 1000);
|
|
||||||
|
|
||||||
describe("Variant Analysis Manager", () => {
|
describe("Variant Analysis Manager", () => {
|
||||||
let cli: CodeQLCliServer;
|
let cli: CodeQLCliServer;
|
||||||
let cancellationTokenSource: CancellationTokenSource;
|
let cancellationTokenSource: CancellationTokenSource;
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import { getActivatedExtension } from "../../global.helper";
|
|||||||
import { createVSCodeCommandManager } from "../../../../src/common/vscode/commands";
|
import { createVSCodeCommandManager } from "../../../../src/common/vscode/commands";
|
||||||
import { AllCommands } from "../../../../src/common/commands";
|
import { AllCommands } from "../../../../src/common/commands";
|
||||||
|
|
||||||
jest.setTimeout(30_000);
|
|
||||||
|
|
||||||
const mockServer = new MockGitHubApiServer();
|
const mockServer = new MockGitHubApiServer();
|
||||||
beforeAll(() => mockServer.startServer());
|
beforeAll(() => mockServer.startServer());
|
||||||
afterEach(() => mockServer.unloadScenario());
|
afterEach(() => mockServer.unloadScenario());
|
||||||
|
|||||||
@@ -687,6 +687,22 @@ describe("local databases", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("when the QL pack already exists", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fs.mkdirSync(join(dir.name, `codeql-custom-queries-${language}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should exit early", async () => {
|
||||||
|
showBinaryChoiceDialogSpy = jest
|
||||||
|
.spyOn(helpers, "showBinaryChoiceDialog")
|
||||||
|
.mockResolvedValue(false);
|
||||||
|
|
||||||
|
await (databaseManager as any).createSkeletonPacks(mockDbItem);
|
||||||
|
|
||||||
|
expect(generateSpy).not.toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("openDatabase", () => {
|
describe("openDatabase", () => {
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { QuickPickItem, window } from "vscode";
|
import { CancellationTokenSource, QuickPickItem, window } from "vscode";
|
||||||
|
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 { pickExtensionPackModelFile } from "../../../../src/data-extensions-editor/extension-pack-picker";
|
||||||
import { QlpacksInfo, ResolveExtensionsResult } from "../../../../src/cli";
|
import { QlpacksInfo, ResolveExtensionsResult } from "../../../../src/cli";
|
||||||
@@ -21,14 +25,33 @@ describe("pickExtensionPackModelFile", () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const databaseItem = {
|
||||||
|
name: "github/vscode-codeql",
|
||||||
|
language: "java",
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
const token = cancellationTokenSource.token;
|
||||||
|
|
||||||
const progress = jest.fn();
|
const progress = jest.fn();
|
||||||
let showQuickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
|
let showQuickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
|
||||||
|
let showInputBoxSpy: jest.SpiedFunction<typeof window.showInputBox>;
|
||||||
|
let showAndLogErrorMessageSpy: jest.SpiedFunction<
|
||||||
|
typeof helpers.showAndLogErrorMessage
|
||||||
|
>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
showQuickPickSpy = jest
|
showQuickPickSpy = jest
|
||||||
.spyOn(window, "showQuickPick")
|
.spyOn(window, "showQuickPick")
|
||||||
.mockRejectedValue(new Error("Unexpected call to showQuickPick"));
|
.mockRejectedValue(new Error("Unexpected call to showQuickPick"));
|
||||||
|
showInputBoxSpy = jest
|
||||||
|
.spyOn(window, "showInputBox")
|
||||||
|
.mockRejectedValue(new Error("Unexpected call to showInputBox"));
|
||||||
|
showAndLogErrorMessageSpy = jest
|
||||||
|
.spyOn(helpers, "showAndLogErrorMessage")
|
||||||
|
.mockImplementation((msg) => {
|
||||||
|
throw new Error(`Unexpected call to showAndLogErrorMessage: ${msg}`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows choosing an existing extension pack and model file", async () => {
|
it("allows choosing an existing extension pack and model file", async () => {
|
||||||
@@ -43,9 +66,14 @@ describe("pickExtensionPackModelFile", () => {
|
|||||||
file: "/a/b/c/my-extension-pack/models/model.yml",
|
file: "/a/b/c/my-extension-pack/models/model.yml",
|
||||||
} as QuickPickItem);
|
} as QuickPickItem);
|
||||||
|
|
||||||
expect(await pickExtensionPackModelFile(cliServer, progress)).toEqual(
|
expect(
|
||||||
"/a/b/c/my-extension-pack/models/model.yml",
|
await pickExtensionPackModelFile(
|
||||||
);
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
).toEqual("/a/b/c/my-extension-pack/models/model.yml");
|
||||||
expect(showQuickPickSpy).toHaveBeenCalledTimes(2);
|
expect(showQuickPickSpy).toHaveBeenCalledTimes(2);
|
||||||
expect(showQuickPickSpy).toHaveBeenCalledWith(
|
expect(showQuickPickSpy).toHaveBeenCalledWith(
|
||||||
[
|
[
|
||||||
@@ -57,10 +85,15 @@ describe("pickExtensionPackModelFile", () => {
|
|||||||
label: "another-extension-pack",
|
label: "another-extension-pack",
|
||||||
extensionPack: "another-extension-pack",
|
extensionPack: "another-extension-pack",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: expect.stringMatching(/create/i),
|
||||||
|
extensionPack: null,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
title: expect.any(String),
|
title: expect.any(String),
|
||||||
},
|
},
|
||||||
|
token,
|
||||||
);
|
);
|
||||||
expect(showQuickPickSpy).toHaveBeenCalledWith(
|
expect(showQuickPickSpy).toHaveBeenCalledWith(
|
||||||
[
|
[
|
||||||
@@ -68,10 +101,15 @@ describe("pickExtensionPackModelFile", () => {
|
|||||||
label: "models/model.yml",
|
label: "models/model.yml",
|
||||||
file: "/a/b/c/my-extension-pack/models/model.yml",
|
file: "/a/b/c/my-extension-pack/models/model.yml",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: expect.stringMatching(/create/i),
|
||||||
|
file: null,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
title: expect.any(String),
|
title: expect.any(String),
|
||||||
},
|
},
|
||||||
|
token,
|
||||||
);
|
);
|
||||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledTimes(1);
|
expect(cliServer.resolveQlpacks).toHaveBeenCalledTimes(1);
|
||||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledWith([], true);
|
expect(cliServer.resolveQlpacks).toHaveBeenCalledWith([], true);
|
||||||
@@ -82,39 +120,300 @@ describe("pickExtensionPackModelFile", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce({
|
||||||
|
label: "my-extension-pack",
|
||||||
|
extensionPack: "my-extension-pack",
|
||||||
|
} as QuickPickItem);
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce({
|
||||||
|
label: "create",
|
||||||
|
file: null,
|
||||||
|
} 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,
|
||||||
|
databaseItem,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
).toEqual(join(tmpDir.path, "models/my-model.yml"));
|
||||||
|
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),
|
||||||
|
value: "models/github.vscode-codeql.model.yml",
|
||||||
|
validateInput: expect.any(Function),
|
||||||
|
},
|
||||||
|
token,
|
||||||
|
);
|
||||||
|
expect(cliServer.resolveQlpacks).toHaveBeenCalledTimes(1);
|
||||||
|
expect(cliServer.resolveQlpacks).toHaveBeenCalledWith([], true);
|
||||||
|
expect(cliServer.resolveExtensions).toHaveBeenCalledTimes(1);
|
||||||
|
expect(cliServer.resolveExtensions).toHaveBeenCalledWith(tmpDir.path, []);
|
||||||
|
});
|
||||||
|
|
||||||
it("allows cancelling the extension pack prompt", async () => {
|
it("allows cancelling the extension pack prompt", async () => {
|
||||||
const cliServer = mockCliServer(qlPacks, extensions);
|
const cliServer = mockCliServer(qlPacks, extensions);
|
||||||
|
|
||||||
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||||
|
|
||||||
expect(await pickExtensionPackModelFile(cliServer, progress)).toEqual(
|
expect(
|
||||||
undefined,
|
await pickExtensionPackModelFile(
|
||||||
);
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
).toEqual(undefined);
|
||||||
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
expect(cliServer.resolveExtensions).not.toHaveBeenCalled();
|
expect(cliServer.resolveExtensions).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not show any options when there are no extension packs", async () => {
|
it("allows user to create an extension pack when there are no extension packs", async () => {
|
||||||
|
const cliServer = mockCliServer({}, { models: [], data: {} });
|
||||||
|
|
||||||
|
const tmpDir = await dir({
|
||||||
|
unsafeCleanup: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce({
|
||||||
|
label: "codeql-custom-queries-java",
|
||||||
|
path: tmpDir.path,
|
||||||
|
} as QuickPickItem);
|
||||||
|
showInputBoxSpy.mockResolvedValueOnce("my-extension-pack");
|
||||||
|
showInputBoxSpy.mockResolvedValue("models/my-model.yml");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await pickExtensionPackModelFile(
|
||||||
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
).toEqual(join(tmpDir.path, "my-extension-pack", "models", "my-model.yml"));
|
||||||
|
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(showInputBoxSpy).toHaveBeenCalledTimes(2);
|
||||||
|
expect(showInputBoxSpy).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
title: expect.stringMatching(/extension pack/i),
|
||||||
|
prompt: expect.stringMatching(/extension pack/i),
|
||||||
|
placeHolder: expect.stringMatching(/github\/vscode-codeql-extensions/),
|
||||||
|
validateInput: expect.any(Function),
|
||||||
|
},
|
||||||
|
token,
|
||||||
|
);
|
||||||
|
expect(showInputBoxSpy).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
title: expect.stringMatching(/model file/),
|
||||||
|
value: "models/github.vscode-codeql.model.yml",
|
||||||
|
validateInput: expect.any(Function),
|
||||||
|
},
|
||||||
|
token,
|
||||||
|
);
|
||||||
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
|
expect(cliServer.resolveExtensions).toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
loadYaml(
|
||||||
|
await readFile(
|
||||||
|
join(tmpDir.path, "my-extension-pack", "codeql-pack.yml"),
|
||||||
|
"utf8",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
name: "my-extension-pack",
|
||||||
|
version: "0.0.0",
|
||||||
|
library: true,
|
||||||
|
extensionTargets: {
|
||||||
|
"codeql/java-all": "*",
|
||||||
|
},
|
||||||
|
dataExtensions: ["models/**/*.yml"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows user to create an extension pack when there are no extension packs with a different language", async () => {
|
||||||
|
const cliServer = mockCliServer({}, { models: [], data: {} });
|
||||||
|
|
||||||
|
const tmpDir = await dir({
|
||||||
|
unsafeCleanup: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce({
|
||||||
|
label: "codeql-custom-queries-java",
|
||||||
|
path: tmpDir.path,
|
||||||
|
} as QuickPickItem);
|
||||||
|
showInputBoxSpy.mockResolvedValueOnce("my-extension-pack");
|
||||||
|
showInputBoxSpy.mockResolvedValue("models/my-model.yml");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await pickExtensionPackModelFile(
|
||||||
|
cliServer,
|
||||||
|
{
|
||||||
|
...databaseItem,
|
||||||
|
language: "csharp",
|
||||||
|
},
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
).toEqual(join(tmpDir.path, "my-extension-pack", "models", "my-model.yml"));
|
||||||
|
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(showInputBoxSpy).toHaveBeenCalledTimes(2);
|
||||||
|
expect(showInputBoxSpy).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
title: expect.stringMatching(/extension pack/i),
|
||||||
|
prompt: expect.stringMatching(/extension pack/i),
|
||||||
|
placeHolder: expect.stringMatching(/github\/vscode-codeql-extensions/),
|
||||||
|
validateInput: expect.any(Function),
|
||||||
|
},
|
||||||
|
token,
|
||||||
|
);
|
||||||
|
expect(showInputBoxSpy).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
title: expect.stringMatching(/model file/),
|
||||||
|
value: "models/github.vscode-codeql.model.yml",
|
||||||
|
validateInput: expect.any(Function),
|
||||||
|
},
|
||||||
|
token,
|
||||||
|
);
|
||||||
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
|
expect(cliServer.resolveExtensions).toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
loadYaml(
|
||||||
|
await readFile(
|
||||||
|
join(tmpDir.path, "my-extension-pack", "codeql-pack.yml"),
|
||||||
|
"utf8",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
name: "my-extension-pack",
|
||||||
|
version: "0.0.0",
|
||||||
|
library: true,
|
||||||
|
extensionTargets: {
|
||||||
|
"codeql/csharp-all": "*",
|
||||||
|
},
|
||||||
|
dataExtensions: ["models/**/*.yml"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows cancelling the workspace folder selection", async () => {
|
||||||
const cliServer = mockCliServer({}, { models: [], data: {} });
|
const cliServer = mockCliServer({}, { models: [], data: {} });
|
||||||
|
|
||||||
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||||
|
|
||||||
expect(await pickExtensionPackModelFile(cliServer, progress)).toEqual(
|
expect(
|
||||||
undefined,
|
await pickExtensionPackModelFile(
|
||||||
);
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
).toEqual(undefined);
|
||||||
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(showQuickPickSpy).toHaveBeenCalledWith([], {
|
expect(showInputBoxSpy).toHaveBeenCalledTimes(0);
|
||||||
title: expect.any(String),
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
});
|
expect(cliServer.resolveExtensions).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows cancelling the extension pack name input", async () => {
|
||||||
|
const cliServer = mockCliServer({}, { models: [], data: {} });
|
||||||
|
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce({
|
||||||
|
label: "codeql-custom-queries-java",
|
||||||
|
path: "/a/b/c",
|
||||||
|
} as QuickPickItem);
|
||||||
|
showInputBoxSpy.mockResolvedValueOnce(undefined);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await pickExtensionPackModelFile(
|
||||||
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
).toEqual(undefined);
|
||||||
|
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(showInputBoxSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
expect(cliServer.resolveExtensions).not.toHaveBeenCalled();
|
expect(cliServer.resolveExtensions).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows an error when an extension pack resolves to more than 1 location", async () => {
|
it("shows an error when an extension pack resolves to more than 1 location", async () => {
|
||||||
const showAndLogErrorMessageSpy = jest.spyOn(
|
showAndLogErrorMessageSpy.mockResolvedValue(undefined);
|
||||||
helpers,
|
|
||||||
"showAndLogErrorMessage",
|
|
||||||
);
|
|
||||||
|
|
||||||
const cliServer = mockCliServer(
|
const cliServer = mockCliServer(
|
||||||
{
|
{
|
||||||
@@ -131,9 +430,14 @@ describe("pickExtensionPackModelFile", () => {
|
|||||||
extensionPack: "my-extension-pack",
|
extensionPack: "my-extension-pack",
|
||||||
} as QuickPickItem);
|
} as QuickPickItem);
|
||||||
|
|
||||||
expect(await pickExtensionPackModelFile(cliServer, progress)).toEqual(
|
expect(
|
||||||
undefined,
|
await pickExtensionPackModelFile(
|
||||||
);
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
).toEqual(undefined);
|
||||||
expect(showAndLogErrorMessageSpy).toHaveBeenCalledTimes(1);
|
expect(showAndLogErrorMessageSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(showAndLogErrorMessageSpy).toHaveBeenCalledWith(
|
expect(showAndLogErrorMessageSpy).toHaveBeenCalledWith(
|
||||||
expect.stringMatching(/could not be resolved to a single location/),
|
expect.stringMatching(/could not be resolved to a single location/),
|
||||||
@@ -153,47 +457,468 @@ describe("pickExtensionPackModelFile", () => {
|
|||||||
} as QuickPickItem);
|
} as QuickPickItem);
|
||||||
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||||
|
|
||||||
expect(await pickExtensionPackModelFile(cliServer, progress)).toEqual(
|
expect(
|
||||||
undefined,
|
await pickExtensionPackModelFile(
|
||||||
);
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
).toEqual(undefined);
|
||||||
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
expect(cliServer.resolveExtensions).toHaveBeenCalled();
|
expect(cliServer.resolveExtensions).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not show any options when there are no model files", async () => {
|
it("shows create input box when there are no model files", async () => {
|
||||||
const cliServer = mockCliServer(qlPacks, { models: [], data: {} });
|
const tmpDir = await dir({
|
||||||
|
unsafeCleanup: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
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"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
showQuickPickSpy.mockResolvedValueOnce({
|
showQuickPickSpy.mockResolvedValueOnce({
|
||||||
label: "my-extension-pack",
|
label: "my-extension-pack",
|
||||||
extensionPack: "my-extension-pack",
|
extensionPack: "my-extension-pack",
|
||||||
} as QuickPickItem);
|
} as QuickPickItem);
|
||||||
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||||
|
showInputBoxSpy.mockResolvedValue("models/my-model.yml");
|
||||||
|
|
||||||
expect(await pickExtensionPackModelFile(cliServer, progress)).toEqual(
|
expect(
|
||||||
undefined,
|
await pickExtensionPackModelFile(
|
||||||
);
|
cliServer,
|
||||||
expect(showQuickPickSpy).toHaveBeenCalledTimes(2);
|
databaseItem,
|
||||||
expect(showQuickPickSpy).toHaveBeenCalledWith(
|
progress,
|
||||||
[
|
token,
|
||||||
{
|
),
|
||||||
label: "my-extension-pack",
|
).toEqual(join(tmpDir.path, "models/my-model.yml"));
|
||||||
extensionPack: "my-extension-pack",
|
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||||
},
|
expect(showInputBoxSpy).toHaveBeenCalledWith(
|
||||||
{
|
|
||||||
label: "another-extension-pack",
|
|
||||||
extensionPack: "another-extension-pack",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{
|
{
|
||||||
title: expect.any(String),
|
title: expect.any(String),
|
||||||
|
value: "models/github.vscode-codeql.model.yml",
|
||||||
|
validateInput: expect.any(Function),
|
||||||
},
|
},
|
||||||
|
token,
|
||||||
);
|
);
|
||||||
expect(showQuickPickSpy).toHaveBeenCalledWith([], {
|
|
||||||
title: expect.any(String),
|
|
||||||
});
|
|
||||||
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
expect(cliServer.resolveExtensions).toHaveBeenCalled();
|
expect(cliServer.resolveExtensions).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("shows an error when there is no pack YAML file", async () => {
|
||||||
|
const tmpDir = await dir({
|
||||||
|
unsafeCleanup: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const cliServer = mockCliServer(
|
||||||
|
{
|
||||||
|
"my-extension-pack": [tmpDir.path],
|
||||||
|
},
|
||||||
|
{ models: [], data: {} },
|
||||||
|
);
|
||||||
|
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce({
|
||||||
|
label: "my-extension-pack",
|
||||||
|
extensionPack: "my-extension-pack",
|
||||||
|
} as QuickPickItem);
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||||
|
showAndLogErrorMessageSpy.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await pickExtensionPackModelFile(
|
||||||
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
).toEqual(undefined);
|
||||||
|
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(showInputBoxSpy).not.toHaveBeenCalled();
|
||||||
|
expect(showAndLogErrorMessageSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(showAndLogErrorMessageSpy).toHaveBeenCalledWith(
|
||||||
|
expect.stringMatching(/codeql-pack\.yml/),
|
||||||
|
);
|
||||||
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
|
expect(cliServer.resolveExtensions).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows an error when the pack YAML file is invalid", async () => {
|
||||||
|
const tmpDir = await dir({
|
||||||
|
unsafeCleanup: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const cliServer = mockCliServer(
|
||||||
|
{
|
||||||
|
"my-extension-pack": [tmpDir.path],
|
||||||
|
},
|
||||||
|
{ models: [], data: {} },
|
||||||
|
);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await pickExtensionPackModelFile(
|
||||||
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
).toEqual(undefined);
|
||||||
|
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(showInputBoxSpy).not.toHaveBeenCalled();
|
||||||
|
expect(showAndLogErrorMessageSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(showAndLogErrorMessageSpy).toHaveBeenCalledWith(
|
||||||
|
expect.stringMatching(/Could not parse/),
|
||||||
|
);
|
||||||
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
|
expect(cliServer.resolveExtensions).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows an error when the pack YAML does not contain dataExtensions", async () => {
|
||||||
|
const tmpDir = await dir({
|
||||||
|
unsafeCleanup: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
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": "*",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce({
|
||||||
|
label: "my-extension-pack",
|
||||||
|
extensionPack: "my-extension-pack",
|
||||||
|
} as QuickPickItem);
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||||
|
showAndLogErrorMessageSpy.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await pickExtensionPackModelFile(
|
||||||
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
).toEqual(undefined);
|
||||||
|
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(showInputBoxSpy).not.toHaveBeenCalled();
|
||||||
|
expect(showAndLogErrorMessageSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(showAndLogErrorMessageSpy).toHaveBeenCalledWith(
|
||||||
|
expect.stringMatching(/Expected 'dataExtensions' to be/),
|
||||||
|
);
|
||||||
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
|
expect(cliServer.resolveExtensions).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows an error when the pack YAML dataExtensions is invalid", async () => {
|
||||||
|
const tmpDir = await dir({
|
||||||
|
unsafeCleanup: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
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: {
|
||||||
|
"codeql/java-all": "invalid",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce({
|
||||||
|
label: "my-extension-pack",
|
||||||
|
extensionPack: "my-extension-pack",
|
||||||
|
} as QuickPickItem);
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||||
|
showAndLogErrorMessageSpy.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await pickExtensionPackModelFile(
|
||||||
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
).toEqual(undefined);
|
||||||
|
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(showInputBoxSpy).not.toHaveBeenCalled();
|
||||||
|
expect(showAndLogErrorMessageSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(showAndLogErrorMessageSpy).toHaveBeenCalledWith(
|
||||||
|
expect.stringMatching(/Expected 'dataExtensions' to be/),
|
||||||
|
);
|
||||||
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
|
expect(cliServer.resolveExtensions).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows cancelling the new file input box", async () => {
|
||||||
|
const tmpDir = await dir({
|
||||||
|
unsafeCleanup: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
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"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce({
|
||||||
|
label: "my-extension-pack",
|
||||||
|
extensionPack: "my-extension-pack",
|
||||||
|
} as QuickPickItem);
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||||
|
showInputBoxSpy.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await pickExtensionPackModelFile(
|
||||||
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
).toEqual(undefined);
|
||||||
|
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(showInputBoxSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
|
expect(cliServer.resolveExtensions).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("validates the pack name input", async () => {
|
||||||
|
const cliServer = mockCliServer({}, { models: [], data: {} });
|
||||||
|
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce({
|
||||||
|
label: "a",
|
||||||
|
path: "/a/b/c",
|
||||||
|
} as QuickPickItem);
|
||||||
|
showInputBoxSpy.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await pickExtensionPackModelFile(
|
||||||
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
).toEqual(undefined);
|
||||||
|
|
||||||
|
const validateFile = showInputBoxSpy.mock.calls[0][0]?.validateInput;
|
||||||
|
expect(validateFile).toBeDefined();
|
||||||
|
if (!validateFile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(await validateFile("")).toEqual("Pack name must not be empty");
|
||||||
|
expect(await validateFile("a".repeat(129))).toEqual(
|
||||||
|
"Pack name must be no longer than 128 characters",
|
||||||
|
);
|
||||||
|
expect(await validateFile("github/vscode-codeql/extensions")).toEqual(
|
||||||
|
"Invalid package name: a pack name must contain only lowercase ASCII letters, ASCII digits, and hyphens",
|
||||||
|
);
|
||||||
|
expect(await validateFile("VSCODE")).toEqual(
|
||||||
|
"Invalid package name: a pack name must contain only lowercase ASCII letters, ASCII digits, and hyphens",
|
||||||
|
);
|
||||||
|
expect(await validateFile("github/vscode-codeql-")).toEqual(
|
||||||
|
"Invalid package name: a pack name must contain only lowercase ASCII letters, ASCII digits, and hyphens",
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await validateFile("github/vscode-codeql-extensions"),
|
||||||
|
).toBeUndefined();
|
||||||
|
expect(await validateFile("vscode-codeql-extensions")).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("validates the file input", async () => {
|
||||||
|
const tmpDir = await dir({
|
||||||
|
unsafeCleanup: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const cliServer = mockCliServer(
|
||||||
|
{
|
||||||
|
"my-extension-pack": [tmpDir.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"),
|
||||||
|
dumpYaml({
|
||||||
|
extensions: [],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce({
|
||||||
|
label: "my-extension-pack",
|
||||||
|
extensionPack: "my-extension-pack",
|
||||||
|
} as QuickPickItem);
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||||
|
showInputBoxSpy.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await pickExtensionPackModelFile(
|
||||||
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
).toEqual(undefined);
|
||||||
|
|
||||||
|
const validateFile = showInputBoxSpy.mock.calls[0][0]?.validateInput;
|
||||||
|
expect(validateFile).toBeDefined();
|
||||||
|
if (!validateFile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(await validateFile("")).toEqual("File name must not be empty");
|
||||||
|
expect(await validateFile("models/model.yml")).toEqual(
|
||||||
|
"File already exists",
|
||||||
|
);
|
||||||
|
expect(await validateFile("../model.yml")).toEqual(
|
||||||
|
"File must be in the extension pack",
|
||||||
|
);
|
||||||
|
expect(await validateFile("/home/user/model.yml")).toEqual(
|
||||||
|
"File must be in the extension pack",
|
||||||
|
);
|
||||||
|
expect(await validateFile("model.yml")).toEqual(
|
||||||
|
`File must match one of the patterns in 'dataExtensions' in ${qlpackPath}`,
|
||||||
|
);
|
||||||
|
expect(await validateFile("models/model.yaml")).toEqual(
|
||||||
|
`File must match one of the patterns in 'dataExtensions' in ${qlpackPath}`,
|
||||||
|
);
|
||||||
|
expect(await validateFile("models/my-model.yml")).toBeUndefined();
|
||||||
|
expect(await validateFile("models/nested/model.yml")).toBeUndefined();
|
||||||
|
expect(await validateFile("data/model.yml")).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the dataExtensions to be a string", async () => {
|
||||||
|
const tmpDir = await dir({
|
||||||
|
unsafeCleanup: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const cliServer = mockCliServer(
|
||||||
|
{
|
||||||
|
"my-extension-pack": [tmpDir.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",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await outputFile(
|
||||||
|
join(tmpDir.path, "models", "model.yml"),
|
||||||
|
dumpYaml({
|
||||||
|
extensions: [],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce({
|
||||||
|
label: "my-extension-pack",
|
||||||
|
extensionPack: "my-extension-pack",
|
||||||
|
} as QuickPickItem);
|
||||||
|
showQuickPickSpy.mockResolvedValueOnce(undefined);
|
||||||
|
showInputBoxSpy.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await pickExtensionPackModelFile(
|
||||||
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
).toEqual(undefined);
|
||||||
|
|
||||||
|
const validateFile = showInputBoxSpy.mock.calls[0][0]?.validateInput;
|
||||||
|
expect(validateFile).toBeDefined();
|
||||||
|
if (!validateFile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(await validateFile("models/my-model.yml")).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function mockCliServer(
|
function mockCliServer(
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import {
|
|||||||
import { createMockLogger } from "../../../__mocks__/loggerMock";
|
import { createMockLogger } from "../../../__mocks__/loggerMock";
|
||||||
import type { Uri } from "vscode";
|
import type { Uri } from "vscode";
|
||||||
import { DatabaseKind } from "../../../../src/local-databases";
|
import { DatabaseKind } from "../../../../src/local-databases";
|
||||||
import * as queryResolver from "../../../../src/contextual/queryResolver";
|
|
||||||
import { file } from "tmp-promise";
|
import { file } from "tmp-promise";
|
||||||
import { QueryResultType } from "../../../../src/pure/new-messages";
|
import { QueryResultType } from "../../../../src/pure/new-messages";
|
||||||
import { readFile } from "fs-extra";
|
import { readdir, readFile } from "fs-extra";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
|
import { dirname, join } from "path";
|
||||||
|
import { fetchExternalApiQueries } from "../../../../src/data-extensions-editor/queries/index";
|
||||||
import * as helpers from "../../../../src/helpers";
|
import * as helpers from "../../../../src/helpers";
|
||||||
import { RedactableError } from "../../../../src/pure/errors";
|
import { RedactableError } from "../../../../src/pure/errors";
|
||||||
|
|
||||||
@@ -27,98 +28,98 @@ function createMockUri(path = "/a/b/c/foo"): Uri {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("runQuery", () => {
|
describe("runQuery", () => {
|
||||||
it("runs the query", async () => {
|
it("runs all queries", async () => {
|
||||||
jest.spyOn(queryResolver, "qlpackOfDatabase").mockResolvedValue({
|
|
||||||
dbschemePack: "codeql/java-all",
|
|
||||||
dbschemePackIsLibraryPack: false,
|
|
||||||
queryPack: "codeql/java-queries",
|
|
||||||
});
|
|
||||||
|
|
||||||
const logPath = (await file()).path;
|
const logPath = (await file()).path;
|
||||||
|
|
||||||
const options = {
|
// Test all queries
|
||||||
cliServer: {
|
for (const [lang, query] of Object.entries(fetchExternalApiQueries)) {
|
||||||
resolveQlpacks: jest.fn().mockResolvedValue({
|
const options = {
|
||||||
"my/java-extensions": "/a/b/c/",
|
cliServer: {
|
||||||
}),
|
resolveQlpacks: jest.fn().mockResolvedValue({
|
||||||
resolveQueriesInSuite: jest
|
"my/extensions": "/a/b/c/",
|
||||||
.fn()
|
|
||||||
.mockResolvedValue([
|
|
||||||
"/home/github/codeql/java/ql/src/Telemetry/FetchExternalAPIs.ql",
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
queryRunner: {
|
|
||||||
createQueryRun: jest.fn().mockReturnValue({
|
|
||||||
evaluate: jest.fn().mockResolvedValue({
|
|
||||||
resultType: QueryResultType.SUCCESS,
|
|
||||||
}),
|
}),
|
||||||
outputDir: {
|
},
|
||||||
logPath,
|
queryRunner: {
|
||||||
|
createQueryRun: jest.fn().mockReturnValue({
|
||||||
|
evaluate: jest.fn().mockResolvedValue({
|
||||||
|
resultType: QueryResultType.SUCCESS,
|
||||||
|
}),
|
||||||
|
outputDir: {
|
||||||
|
logPath,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
logger: createMockLogger(),
|
||||||
|
},
|
||||||
|
databaseItem: {
|
||||||
|
databaseUri: createMockUri("/a/b/c/src.zip"),
|
||||||
|
contents: {
|
||||||
|
kind: DatabaseKind.Database,
|
||||||
|
name: "foo",
|
||||||
|
datasetUri: createMockUri(),
|
||||||
},
|
},
|
||||||
}),
|
language: lang,
|
||||||
logger: createMockLogger(),
|
|
||||||
},
|
|
||||||
logger: createMockLogger(),
|
|
||||||
databaseItem: {
|
|
||||||
databaseUri: createMockUri("/a/b/c/src.zip"),
|
|
||||||
contents: {
|
|
||||||
kind: DatabaseKind.Database,
|
|
||||||
name: "foo",
|
|
||||||
datasetUri: createMockUri(),
|
|
||||||
},
|
},
|
||||||
language: "java",
|
queryStorageDir: "/tmp/queries",
|
||||||
},
|
progress: jest.fn(),
|
||||||
queryStorageDir: "/tmp/queries",
|
token: {
|
||||||
progress: jest.fn(),
|
isCancellationRequested: false,
|
||||||
token: {
|
onCancellationRequested: jest.fn(),
|
||||||
isCancellationRequested: false,
|
|
||||||
onCancellationRequested: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const result = await runQuery(options);
|
|
||||||
|
|
||||||
expect(result?.resultType).toEqual(QueryResultType.SUCCESS);
|
|
||||||
|
|
||||||
expect(options.cliServer.resolveQueriesInSuite).toHaveBeenCalledWith(
|
|
||||||
expect.anything(),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
const suiteFile = options.cliServer.resolveQueriesInSuite.mock.calls[0][0];
|
|
||||||
const suiteFileContents = await readFile(suiteFile, "utf8");
|
|
||||||
const suiteYaml = load(suiteFileContents);
|
|
||||||
expect(suiteYaml).toEqual([
|
|
||||||
{
|
|
||||||
from: "codeql/java-all",
|
|
||||||
queries: ".",
|
|
||||||
include: {
|
|
||||||
id: "java/telemetry/fetch-external-apis",
|
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
{
|
const result = await runQuery(options);
|
||||||
from: "codeql/java-queries",
|
|
||||||
queries: ".",
|
|
||||||
include: {
|
|
||||||
id: "java/telemetry/fetch-external-apis",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(options.cliServer.resolveQlpacks).toHaveBeenCalledTimes(1);
|
expect(result?.resultType).toEqual(QueryResultType.SUCCESS);
|
||||||
expect(options.cliServer.resolveQlpacks).toHaveBeenCalledWith([], true);
|
|
||||||
expect(options.queryRunner.createQueryRun).toHaveBeenCalledWith(
|
expect(options.cliServer.resolveQlpacks).toHaveBeenCalledTimes(1);
|
||||||
"/a/b/c/src.zip",
|
expect(options.cliServer.resolveQlpacks).toHaveBeenCalledWith([], true);
|
||||||
{
|
expect(options.queryRunner.createQueryRun).toHaveBeenCalledWith(
|
||||||
queryPath:
|
"/a/b/c/src.zip",
|
||||||
"/home/github/codeql/java/ql/src/Telemetry/FetchExternalAPIs.ql",
|
{
|
||||||
quickEvalPosition: undefined,
|
queryPath: expect.stringMatching(/FetchExternalApis\.ql/),
|
||||||
},
|
quickEvalPosition: undefined,
|
||||||
false,
|
},
|
||||||
[],
|
false,
|
||||||
["my/java-extensions"],
|
[],
|
||||||
"/tmp/queries",
|
["my/extensions"],
|
||||||
undefined,
|
"/tmp/queries",
|
||||||
undefined,
|
undefined,
|
||||||
);
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const queryPath =
|
||||||
|
options.queryRunner.createQueryRun.mock.calls[0][1].queryPath;
|
||||||
|
const queryDirectory = dirname(queryPath);
|
||||||
|
|
||||||
|
const queryFiles = await readdir(queryDirectory);
|
||||||
|
expect(queryFiles.sort()).toEqual(
|
||||||
|
["codeql-pack.yml", "FetchExternalApis.ql", "ExternalApi.qll"].sort(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const suiteFileContents = await readFile(
|
||||||
|
join(queryDirectory, "codeql-pack.yml"),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
const suiteYaml = load(suiteFileContents);
|
||||||
|
expect(suiteYaml).toEqual({
|
||||||
|
name: "codeql/external-api-usage",
|
||||||
|
version: "0.0.0",
|
||||||
|
dependencies: {
|
||||||
|
[`codeql/${lang}-all`]: "*",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await readFile(join(queryDirectory, "FetchExternalApis.ql"), "utf8"),
|
||||||
|
).toEqual(query.mainQuery);
|
||||||
|
|
||||||
|
for (const [filename, contents] of Object.entries(
|
||||||
|
query.dependencies ?? {},
|
||||||
|
)) {
|
||||||
|
expect(await readFile(join(queryDirectory, filename), "utf8")).toEqual(
|
||||||
|
contents,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,11 @@
|
|||||||
"completed": true,
|
"completed": true,
|
||||||
"variantAnalysis": {
|
"variantAnalysis": {
|
||||||
"id": 98574321397,
|
"id": 98574321397,
|
||||||
"controllerRepoId": 128321,
|
"controllerRepo": {
|
||||||
|
"id": 128321,
|
||||||
|
"fullName": "github/codeql",
|
||||||
|
"private": false
|
||||||
|
},
|
||||||
"query": {
|
"query": {
|
||||||
"name": "Variant Analysis Integration Test 1",
|
"name": "Variant Analysis Integration Test 1",
|
||||||
"filePath": "PLACEHOLDER/q2.ql",
|
"filePath": "PLACEHOLDER/q2.ql",
|
||||||
@@ -30,7 +34,11 @@
|
|||||||
"completed": true,
|
"completed": true,
|
||||||
"variantAnalysis": {
|
"variantAnalysis": {
|
||||||
"id": 98574321397,
|
"id": 98574321397,
|
||||||
"controllerRepoId": 128321,
|
"controllerRepo": {
|
||||||
|
"id": 128321,
|
||||||
|
"fullName": "github/codeql",
|
||||||
|
"private": false
|
||||||
|
},
|
||||||
"query": {
|
"query": {
|
||||||
"name": "Variant Analysis Integration Test 2",
|
"name": "Variant Analysis Integration Test 2",
|
||||||
"filePath": "PLACEHOLDER/q2.ql",
|
"filePath": "PLACEHOLDER/q2.ql",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
import { DirResult } from "tmp";
|
import { DirResult } from "tmp";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
getFirstWorkspaceFolder,
|
||||||
getInitialQueryContents,
|
getInitialQueryContents,
|
||||||
InvocationRateLimiter,
|
InvocationRateLimiter,
|
||||||
isFolderAlreadyInWorkspace,
|
isFolderAlreadyInWorkspace,
|
||||||
@@ -272,6 +273,13 @@ describe("helpers", () => {
|
|||||||
class MockEnvironmentVariableCollection
|
class MockEnvironmentVariableCollection
|
||||||
implements EnvironmentVariableCollection
|
implements EnvironmentVariableCollection
|
||||||
{
|
{
|
||||||
|
[Symbol.iterator](): Iterator<
|
||||||
|
[variable: string, mutator: EnvironmentVariableMutator],
|
||||||
|
any,
|
||||||
|
undefined
|
||||||
|
> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
persistent = false;
|
persistent = false;
|
||||||
replace(_variable: string, _value: string): void {
|
replace(_variable: string, _value: string): void {
|
||||||
throw new Error("Method not implemented.");
|
throw new Error("Method not implemented.");
|
||||||
@@ -671,3 +679,42 @@ describe("prepareCodeTour", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("getFirstWorkspaceFolder", () => {
|
||||||
|
it("should return the first workspace folder", async () => {
|
||||||
|
jest.spyOn(workspace, "workspaceFolders", "get").mockReturnValue([
|
||||||
|
{
|
||||||
|
name: "codespaces-codeql",
|
||||||
|
uri: { fsPath: "codespaces-codeql", scheme: "file" },
|
||||||
|
},
|
||||||
|
] as WorkspaceFolder[]);
|
||||||
|
|
||||||
|
expect(getFirstWorkspaceFolder()).toEqual("codespaces-codeql");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("if user is in vscode-codeql-starter workspace", () => {
|
||||||
|
it("should set storage path to parent folder", async () => {
|
||||||
|
jest.spyOn(workspace, "workspaceFolders", "get").mockReturnValue([
|
||||||
|
{
|
||||||
|
name: "codeql-custom-queries-cpp",
|
||||||
|
uri: {
|
||||||
|
fsPath: join("vscode-codeql-starter", "codeql-custom-queries-cpp"),
|
||||||
|
scheme: "file",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "codeql-custom-queries-csharp",
|
||||||
|
uri: {
|
||||||
|
fsPath: join(
|
||||||
|
"vscode-codeql-starter",
|
||||||
|
"codeql-custom-queries-csharp",
|
||||||
|
),
|
||||||
|
scheme: "file",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as WorkspaceFolder[]);
|
||||||
|
|
||||||
|
expect(getFirstWorkspaceFolder()).toEqual("vscode-codeql-starter");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user