Merge remote-tracking branch 'origin/main' into dbartol/save-before-start
This commit is contained in:
@@ -2,9 +2,8 @@
|
||||
* @name Unwanted dependency on vscode API
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @id vscode-codeql/assert-pure
|
||||
* @description The modules stored under `pure` and tested in the `pure-tests`
|
||||
* are intended to be "pure".
|
||||
* @id vscode-codeql/assert-no-vscode-dependency
|
||||
* @description The modules stored under `common` should not have dependencies on the VS Code API
|
||||
*/
|
||||
|
||||
import javascript
|
||||
@@ -13,12 +12,9 @@ class VSCodeImport extends ImportDeclaration {
|
||||
VSCodeImport() { this.getImportedPath().getValue() = "vscode" }
|
||||
}
|
||||
|
||||
class PureFile extends File {
|
||||
PureFile() {
|
||||
(
|
||||
this.getRelativePath().regexpMatch(".*/src/pure/.*") or
|
||||
this.getRelativePath().regexpMatch(".*/src/common/.*")
|
||||
) and
|
||||
class CommonFile extends File {
|
||||
CommonFile() {
|
||||
this.getRelativePath().regexpMatch(".*/src/common/.*") and
|
||||
not this.getRelativePath().regexpMatch(".*/vscode/.*")
|
||||
}
|
||||
}
|
||||
@@ -34,7 +30,8 @@ query predicate edges(AstNode a, AstNode b) {
|
||||
|
||||
from Module m, VSCodeImport v
|
||||
where
|
||||
m.getFile() instanceof PureFile and
|
||||
m.getFile() instanceof CommonFile and
|
||||
edges+(m, v)
|
||||
select m, m, v,
|
||||
"This module is not pure: it has a transitive dependency on the vscode API imported $@", v, "here"
|
||||
"This module is in the 'common' directory but has a transitive dependency on the vscode API imported $@",
|
||||
v, "here"
|
||||
@@ -26,7 +26,7 @@ import { walkDirectory } from "../common/files";
|
||||
import { QueryMetadata, SortDirection } from "../common/interface-types";
|
||||
import { BaseLogger, Logger } from "../common/logging";
|
||||
import { ProgressReporter } from "../common/logging/vscode";
|
||||
import { CompilationMessage } from "../pure/legacy-messages";
|
||||
import { CompilationMessage } from "../query-server/legacy-messages";
|
||||
import { sarifParser } from "../common/sarif-parser";
|
||||
import { App } from "../common/app";
|
||||
import { QueryLanguage } from "../common/query-language";
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { AstItem } from "../language-support";
|
||||
import type { DbTreeViewItem } from "../databases/ui/db-tree-view-item";
|
||||
import type { DatabaseItem } from "../databases/local-databases";
|
||||
import type { QueryHistoryInfo } from "../query-history/query-history-info";
|
||||
import type { RepositoriesFilterSortStateWithIds } from "../pure/variant-analysis-filter-sort";
|
||||
import type { RepositoriesFilterSortStateWithIds } from "../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import type { TestTreeNode } from "../query-testing/test-tree-node";
|
||||
import type {
|
||||
VariantAnalysis,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { pathExists, stat, readdir, opendir } from "fs-extra";
|
||||
import { isAbsolute, join, relative, resolve } from "path";
|
||||
import { tmpdir as osTmpdir } from "os";
|
||||
|
||||
/**
|
||||
* Recursively finds all .ql files in this set of Uris.
|
||||
@@ -121,3 +122,8 @@ export interface IOError {
|
||||
export function isIOError(e: any): e is IOError {
|
||||
return e.code !== undefined && typeof e.code === "string";
|
||||
}
|
||||
|
||||
// This function is a wrapper around `os.tmpdir()` to make it easier to mock in tests.
|
||||
export function tmpdir(): string {
|
||||
return osTmpdir();
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import {
|
||||
RepositoriesFilterSortState,
|
||||
RepositoriesFilterSortStateWithIds,
|
||||
} from "../pure/variant-analysis-filter-sort";
|
||||
} from "../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import { ErrorLike } from "../common/errors";
|
||||
import { DataFlowPaths } from "../variant-analysis/shared/data-flow-paths";
|
||||
import { ExternalApiUsage } from "../data-extensions-editor/external-api-usage";
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
FilterKey,
|
||||
SortKey,
|
||||
defaultFilterSortState,
|
||||
} from "./pure/variant-analysis-filter-sort";
|
||||
} from "./variant-analysis/shared/variant-analysis-filter-sort";
|
||||
|
||||
export const ALL_SETTINGS: Setting[] = [];
|
||||
|
||||
@@ -731,7 +731,15 @@ export function showQueriesPanel(): boolean {
|
||||
|
||||
const DATA_EXTENSIONS = new Setting("dataExtensions", ROOT_SETTING);
|
||||
const LLM_GENERATION = new Setting("llmGeneration", DATA_EXTENSIONS);
|
||||
const DISABLE_AUTO_NAME_EXTENSION_PACK = new Setting(
|
||||
"disableAutoNameExtensionPack",
|
||||
DATA_EXTENSIONS,
|
||||
);
|
||||
|
||||
export function showLlmGeneration(): boolean {
|
||||
return !!LLM_GENERATION.getValue<boolean>();
|
||||
}
|
||||
|
||||
export function disableAutoNameExtensionPack(): boolean {
|
||||
return !!DISABLE_AUTO_NAME_EXTENSION_PACK.getValue<boolean>();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
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 interface ExtensionPackName {
|
||||
scope: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function formatPackName(packName: ExtensionPackName): string {
|
||||
return `${packName.scope}/${packName.name}`;
|
||||
}
|
||||
|
||||
export function autoNameExtensionPack(
|
||||
name: string,
|
||||
language: string,
|
||||
): ExtensionPackName | undefined {
|
||||
let packName = `${name}-${language}`;
|
||||
if (!packName.includes("/")) {
|
||||
packName = `pack/${packName}`;
|
||||
}
|
||||
|
||||
const parts = packName.split("/");
|
||||
const sanitizedParts = parts.map((part) => sanitizeExtensionPackName(part));
|
||||
|
||||
// If the scope is empty (e.g. if the given name is "-/b"), then we need to still set a scope
|
||||
if (sanitizedParts[0].length === 0) {
|
||||
sanitizedParts[0] = "pack";
|
||||
}
|
||||
|
||||
return {
|
||||
scope: sanitizedParts[0],
|
||||
// This will ensure there's only 1 slash
|
||||
name: sanitizedParts.slice(1).join("-"),
|
||||
};
|
||||
}
|
||||
|
||||
function sanitizeExtensionPackName(name: string) {
|
||||
// Lowercase everything
|
||||
name = name.toLowerCase();
|
||||
|
||||
// Replace all spaces, dots, and underscores with hyphens
|
||||
name = name.replaceAll(/[\s._]+/g, "-");
|
||||
|
||||
// Replace all characters which are not allowed by empty strings
|
||||
name = name.replaceAll(/[^a-z0-9-]/g, "");
|
||||
|
||||
// Remove any leading or trailing hyphens
|
||||
name = name.replaceAll(/^-|-$/g, "");
|
||||
|
||||
// Remove any duplicate hyphens
|
||||
name = name.replaceAll(/-{2,}/g, "-");
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
export function parsePackName(packName: string): ExtensionPackName | undefined {
|
||||
const matches = packNameRegex.exec(packName);
|
||||
if (!matches?.groups) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scope = matches.groups.scope;
|
||||
const name = matches.groups.name;
|
||||
|
||||
return {
|
||||
scope,
|
||||
name,
|
||||
};
|
||||
}
|
||||
|
||||
export function validatePackName(name: string): string | undefined {
|
||||
if (!name) {
|
||||
return "Pack name must not be empty";
|
||||
}
|
||||
|
||||
if (name.length > packNameLength) {
|
||||
return `Pack name must be no longer than ${packNameLength} characters`;
|
||||
}
|
||||
|
||||
const matches = packNameRegex.exec(name);
|
||||
if (!matches?.groups) {
|
||||
if (!name.includes("/")) {
|
||||
return "Invalid package name: a pack name must contain a slash to separate the scope from the pack name";
|
||||
}
|
||||
|
||||
return "Invalid package name: a pack name must contain only lowercase ASCII letters, ASCII digits, and hyphens";
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
@@ -3,11 +3,8 @@ 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 "../codeql-cli/cli";
|
||||
import {
|
||||
getOnDiskWorkspaceFolders,
|
||||
getOnDiskWorkspaceFoldersObjects,
|
||||
} from "../common/vscode/workspace-folders";
|
||||
import { CodeQLCliServer, QlpacksInfo } from "../codeql-cli/cli";
|
||||
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||
import { ProgressCallback } from "../common/vscode/progress";
|
||||
import { DatabaseItem } from "../databases/local-databases";
|
||||
import { getQlPackPath, QLPACK_FILENAMES } from "../common/ql";
|
||||
@@ -15,15 +12,21 @@ import { getErrorMessage } from "../common/helpers-pure";
|
||||
import { ExtensionPack, ExtensionPackModelFile } from "./shared/extension-pack";
|
||||
import { NotificationLogger, showAndLogErrorMessage } from "../common/logging";
|
||||
import { containsPath } from "../common/files";
|
||||
import { disableAutoNameExtensionPack } from "../config";
|
||||
import {
|
||||
autoNameExtensionPack,
|
||||
ExtensionPackName,
|
||||
formatPackName,
|
||||
parsePackName,
|
||||
validatePackName,
|
||||
} from "./extension-pack-name";
|
||||
import {
|
||||
askForWorkspaceFolder,
|
||||
autoPickExtensionsDirectory,
|
||||
} from "./extensions-workspace-folder";
|
||||
|
||||
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(
|
||||
cliServer: Pick<CodeQLCliServer, "resolveQlpacks" | "resolveExtensions">,
|
||||
databaseItem: Pick<DatabaseItem, "name" | "language">,
|
||||
@@ -79,6 +82,21 @@ async function pickExtensionPack(
|
||||
true,
|
||||
);
|
||||
|
||||
if (!disableAutoNameExtensionPack()) {
|
||||
progress({
|
||||
message: "Creating extension pack...",
|
||||
step: 2,
|
||||
maxStep,
|
||||
});
|
||||
|
||||
return autoCreateExtensionPack(
|
||||
databaseItem.name,
|
||||
databaseItem.language,
|
||||
extensionPacksInfo,
|
||||
logger,
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(extensionPacksInfo).length === 0) {
|
||||
return pickNewExtensionPack(databaseItem, token);
|
||||
}
|
||||
@@ -239,51 +257,35 @@ async function pickNewExtensionPack(
|
||||
databaseItem: Pick<DatabaseItem, "name" | "language">,
|
||||
token: CancellationToken,
|
||||
): Promise<ExtensionPack | 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",
|
||||
});
|
||||
const workspaceFolder = await askForWorkspaceFolder();
|
||||
if (!workspaceFolder) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let examplePackName = `${databaseItem.name}-extensions`;
|
||||
if (!examplePackName.includes("/")) {
|
||||
examplePackName = `pack/${examplePackName}`;
|
||||
}
|
||||
const examplePackName = autoNameExtensionPack(
|
||||
databaseItem.name,
|
||||
databaseItem.language,
|
||||
);
|
||||
|
||||
const packName = await window.showInputBox(
|
||||
const name = await window.showInputBox(
|
||||
{
|
||||
title: "Create new extension pack",
|
||||
prompt: "Enter name of extension pack",
|
||||
placeHolder: `e.g. ${examplePackName}`,
|
||||
placeHolder: examplePackName
|
||||
? `e.g. ${formatPackName(examplePackName)}`
|
||||
: "",
|
||||
validateInput: async (value: string): Promise<string | undefined> => {
|
||||
if (!value) {
|
||||
return "Pack name must not be empty";
|
||||
const message = validatePackName(value);
|
||||
if (message) {
|
||||
return message;
|
||||
}
|
||||
|
||||
if (value.length > packNameLength) {
|
||||
return `Pack name must be no longer than ${packNameLength} characters`;
|
||||
const packName = parsePackName(value);
|
||||
if (!packName) {
|
||||
return "Invalid pack name";
|
||||
}
|
||||
|
||||
const matches = packNameRegex.exec(value);
|
||||
if (!matches?.groups) {
|
||||
if (!value.includes("/")) {
|
||||
return "Invalid package name: a pack name must contain a slash to separate the scope from the pack name";
|
||||
}
|
||||
|
||||
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);
|
||||
const packPath = join(workspaceFolder.uri.fsPath, packName.name);
|
||||
if (await pathExists(packPath)) {
|
||||
return `A pack already exists at ${packPath}`;
|
||||
}
|
||||
@@ -293,31 +295,121 @@ async function pickNewExtensionPack(
|
||||
},
|
||||
token,
|
||||
);
|
||||
if (!name) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const packName = parsePackName(name);
|
||||
if (!packName) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const matches = packNameRegex.exec(packName);
|
||||
if (!matches?.groups) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = matches.groups.name;
|
||||
const packPath = join(workspaceFolder.path, name);
|
||||
const packPath = join(workspaceFolder.uri.fsPath, packName.name);
|
||||
|
||||
if (await pathExists(packPath)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return writeExtensionPack(packPath, packName, databaseItem.language);
|
||||
}
|
||||
|
||||
async function autoCreateExtensionPack(
|
||||
name: string,
|
||||
language: string,
|
||||
extensionPacksInfo: QlpacksInfo,
|
||||
logger: NotificationLogger,
|
||||
): Promise<ExtensionPack | undefined> {
|
||||
// Get the extensions directory to create the extension pack in
|
||||
const extensionsDirectory = await autoPickExtensionsDirectory();
|
||||
if (!extensionsDirectory) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Generate the name of the extension pack
|
||||
const packName = autoNameExtensionPack(name, language);
|
||||
if (!packName) {
|
||||
void showAndLogErrorMessage(
|
||||
logger,
|
||||
`Could not automatically name extension pack for database ${name}`,
|
||||
);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Find any existing locations of this extension pack
|
||||
const existingExtensionPackPaths =
|
||||
extensionPacksInfo[formatPackName(packName)];
|
||||
|
||||
// If there is already an extension pack with this name, use it if it is valid
|
||||
if (existingExtensionPackPaths?.length === 1) {
|
||||
let extensionPack: ExtensionPack;
|
||||
try {
|
||||
extensionPack = await readExtensionPack(existingExtensionPackPaths[0]);
|
||||
} catch (e: unknown) {
|
||||
void showAndLogErrorMessage(
|
||||
logger,
|
||||
`Could not read extension pack ${formatPackName(packName)}`,
|
||||
{
|
||||
fullMessage: `Could not read extension pack ${formatPackName(
|
||||
packName,
|
||||
)} at ${existingExtensionPackPaths[0]}: ${getErrorMessage(e)}`,
|
||||
},
|
||||
);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return extensionPack;
|
||||
}
|
||||
|
||||
// If there is already an existing extension pack with this name, but it resolves
|
||||
// to multiple paths, then we can't use it
|
||||
if (existingExtensionPackPaths?.length > 1) {
|
||||
void showAndLogErrorMessage(
|
||||
logger,
|
||||
`Extension pack ${formatPackName(packName)} resolves to multiple paths`,
|
||||
{
|
||||
fullMessage: `Extension pack ${formatPackName(
|
||||
packName,
|
||||
)} resolves to multiple paths: ${existingExtensionPackPaths.join(
|
||||
", ",
|
||||
)}`,
|
||||
},
|
||||
);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const packPath = join(extensionsDirectory.fsPath, packName.name);
|
||||
|
||||
if (await pathExists(packPath)) {
|
||||
void showAndLogErrorMessage(
|
||||
logger,
|
||||
`Directory ${packPath} already exists for extension pack ${formatPackName(
|
||||
packName,
|
||||
)}`,
|
||||
);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return writeExtensionPack(packPath, packName, language);
|
||||
}
|
||||
|
||||
async function writeExtensionPack(
|
||||
packPath: string,
|
||||
packName: ExtensionPackName,
|
||||
language: string,
|
||||
): Promise<ExtensionPack> {
|
||||
const packYamlPath = join(packPath, "codeql-pack.yml");
|
||||
|
||||
const extensionPack: ExtensionPack = {
|
||||
path: packPath,
|
||||
yamlPath: packYamlPath,
|
||||
name: packName,
|
||||
name: formatPackName(packName),
|
||||
version: "0.0.0",
|
||||
extensionTargets: {
|
||||
[`codeql/${databaseItem.language}-all`]: "*",
|
||||
[`codeql/${language}-all`]: "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml"],
|
||||
};
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
import { FileType, Uri, window, workspace, WorkspaceFolder } from "vscode";
|
||||
import { getOnDiskWorkspaceFoldersObjects } from "../common/vscode/workspace-folders";
|
||||
import { extLogger } from "../common/logging/vscode";
|
||||
import { tmpdir } from "../common/files";
|
||||
|
||||
/**
|
||||
* Returns the ancestors of this path in order from furthest to closest (i.e. root of filesystem to parent directory)
|
||||
*/
|
||||
function getAncestors(uri: Uri): Uri[] {
|
||||
const ancestors: Uri[] = [];
|
||||
let current = uri;
|
||||
while (current.fsPath !== Uri.joinPath(current, "..").fsPath) {
|
||||
ancestors.push(current);
|
||||
current = Uri.joinPath(current, "..");
|
||||
}
|
||||
|
||||
// The ancestors are now in order from closest to furthest, so reverse them
|
||||
ancestors.reverse();
|
||||
|
||||
return ancestors;
|
||||
}
|
||||
|
||||
async function getRootWorkspaceDirectory(): Promise<Uri | undefined> {
|
||||
// If there is a valid workspace file, just use its directory as the directory for the extensions
|
||||
const workspaceFile = workspace.workspaceFile;
|
||||
if (workspaceFile?.scheme === "file") {
|
||||
return Uri.joinPath(workspaceFile, "..");
|
||||
}
|
||||
|
||||
const allWorkspaceFolders = getOnDiskWorkspaceFoldersObjects();
|
||||
|
||||
// Get the system temp directory and convert it to a URI so it's normalized
|
||||
const systemTmpdir = Uri.file(tmpdir());
|
||||
|
||||
const workspaceFolders = allWorkspaceFolders.filter((folder) => {
|
||||
// Never use a workspace folder that is in the system temp directory
|
||||
return !folder.uri.fsPath.startsWith(systemTmpdir.fsPath);
|
||||
});
|
||||
|
||||
// Find the common root directory of all workspace folders by finding the longest common prefix
|
||||
const commonRoot = workspaceFolders.reduce((commonRoot, folder) => {
|
||||
const folderUri = folder.uri;
|
||||
const ancestors = getAncestors(folderUri);
|
||||
|
||||
const minLength = Math.min(commonRoot.length, ancestors.length);
|
||||
let commonLength = 0;
|
||||
for (let i = 0; i < minLength; i++) {
|
||||
if (commonRoot[i].fsPath === ancestors[i].fsPath) {
|
||||
commonLength++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return commonRoot.slice(0, commonLength);
|
||||
}, getAncestors(workspaceFolders[0].uri));
|
||||
|
||||
if (commonRoot.length === 0) {
|
||||
return await findGitFolder(workspaceFolders);
|
||||
}
|
||||
|
||||
// The path closest to the workspace folders is the last element of the common root
|
||||
const commonRootUri = commonRoot[commonRoot.length - 1];
|
||||
|
||||
// If we are at the root of the filesystem, we can't go up any further and there's something
|
||||
// wrong, so just return undefined
|
||||
if (commonRootUri.fsPath === Uri.joinPath(commonRootUri, "..").fsPath) {
|
||||
return await findGitFolder(workspaceFolders);
|
||||
}
|
||||
|
||||
return commonRootUri;
|
||||
}
|
||||
|
||||
async function findGitFolder(
|
||||
workspaceFolders: WorkspaceFolder[],
|
||||
): Promise<Uri | undefined> {
|
||||
// Go through all workspace folders one-by-one and try to find the closest .git folder for each one
|
||||
const folders = await Promise.all(
|
||||
workspaceFolders.map(async (folder) => {
|
||||
const ancestors = getAncestors(folder.uri);
|
||||
|
||||
// Reverse the ancestors so we're going from closest to furthest
|
||||
ancestors.reverse();
|
||||
|
||||
const gitFoldersExists = await Promise.all(
|
||||
ancestors.map(async (uri) => {
|
||||
const gitFolder = Uri.joinPath(uri, ".git");
|
||||
try {
|
||||
const stat = await workspace.fs.stat(gitFolder);
|
||||
// Check whether it's a directory
|
||||
return (stat.type & FileType.Directory) !== 0;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
// Find the first ancestor that has a .git folder
|
||||
const ancestorIndex = gitFoldersExists.findIndex((exists) => exists);
|
||||
|
||||
if (ancestorIndex === -1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [ancestorIndex, ancestors[ancestorIndex]];
|
||||
}),
|
||||
);
|
||||
|
||||
const validFolders = folders.filter(
|
||||
(folder): folder is [number, Uri] => folder !== undefined,
|
||||
);
|
||||
if (validFolders.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Find the .git folder which is closest to a workspace folder
|
||||
const closestFolder = validFolders.reduce((closestFolder, folder) => {
|
||||
if (folder[0] < closestFolder[0]) {
|
||||
return folder;
|
||||
}
|
||||
return closestFolder;
|
||||
}, validFolders[0]);
|
||||
|
||||
return closestFolder?.[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a suitable directory for extension packs to be created in. This will
|
||||
* always be a path ending in `.github/codeql/extensions`. The parent directory
|
||||
* will be determined heuristically based on the on-disk workspace folders.
|
||||
*
|
||||
* The heuristic is as follows (`.github/codeql/extensions` is added automatically unless
|
||||
* otherwise specified):
|
||||
* 1. If there is only 1 workspace folder, use that folder
|
||||
* 2. If there is a workspace folder for which the path ends in `.github/codeql/extensions`, use that folder
|
||||
* - If there are multiple such folders, use the first one
|
||||
* - Does not append `.github/codeql/extensions` to the path
|
||||
* 3. If there is a workspace file (`<basename>.code-workspace`), use the directory containing that file
|
||||
* 4. If there is a common root directory for all workspace folders, use that directory
|
||||
* - Workspace folders in the system temp directory are ignored
|
||||
* - If the common root directory is the root of the filesystem, then it's not used
|
||||
* 5. If there is a .git directory in any workspace folder, use the directory containing that .git directory
|
||||
* for which the .git directory is closest to a workspace folder
|
||||
* 6. If none of the above apply, return `undefined`
|
||||
*/
|
||||
export async function autoPickExtensionsDirectory(): Promise<Uri | undefined> {
|
||||
const workspaceFolders = getOnDiskWorkspaceFoldersObjects();
|
||||
|
||||
// If there's only 1 workspace folder, use the `.github/codeql/extensions` directory in that folder
|
||||
if (workspaceFolders.length === 1) {
|
||||
return Uri.joinPath(
|
||||
workspaceFolders[0].uri,
|
||||
".github",
|
||||
"codeql",
|
||||
"extensions",
|
||||
);
|
||||
}
|
||||
|
||||
// Now try to find a workspace folder for which the path ends in `.github/codeql/extensions`
|
||||
const workspaceFolderForExtensions = workspaceFolders.find((folder) =>
|
||||
// Using path instead of fsPath because path always uses forward slashes
|
||||
folder.uri.path.endsWith(".github/codeql/extensions"),
|
||||
);
|
||||
if (workspaceFolderForExtensions) {
|
||||
return workspaceFolderForExtensions.uri;
|
||||
}
|
||||
|
||||
// Get the root workspace directory, i.e. the common root directory of all workspace folders
|
||||
const rootDirectory = await getRootWorkspaceDirectory();
|
||||
if (!rootDirectory) {
|
||||
void extLogger.log("Unable to determine root workspace directory");
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// We'll create a new workspace folder for the extensions in the root workspace directory
|
||||
// at `.github/codeql/extensions`
|
||||
const extensionsUri = Uri.joinPath(
|
||||
rootDirectory,
|
||||
".github",
|
||||
"codeql",
|
||||
"extensions",
|
||||
);
|
||||
|
||||
if (
|
||||
!workspace.updateWorkspaceFolders(
|
||||
workspace.workspaceFolders?.length ?? 0,
|
||||
0,
|
||||
{
|
||||
name: "CodeQL Extension Packs",
|
||||
uri: extensionsUri,
|
||||
},
|
||||
)
|
||||
) {
|
||||
void extLogger.log(
|
||||
`Failed to add workspace folder for extensions at ${extensionsUri.fsPath}`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return extensionsUri;
|
||||
}
|
||||
|
||||
export async function askForWorkspaceFolder(): Promise<
|
||||
WorkspaceFolder | undefined
|
||||
> {
|
||||
const workspaceFolders = getOnDiskWorkspaceFoldersObjects();
|
||||
const workspaceFolderOptions = workspaceFolders.map((folder) => ({
|
||||
label: folder.name,
|
||||
detail: folder.uri.fsPath,
|
||||
folder,
|
||||
}));
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
return workspaceFolder.folder;
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { DatabaseItem } from "../databases/local-databases";
|
||||
import { ProgressCallback } from "../common/vscode/progress";
|
||||
import { fetchExternalApiQueries } from "./queries";
|
||||
import { QueryResultType } from "../pure/new-messages";
|
||||
import { QueryResultType } from "../query-server/new-messages";
|
||||
import { join } from "path";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { telemetryListener } from "../common/vscode/telemetry";
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
ModeledMethodWithSignature,
|
||||
} from "./modeled-method";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { QueryResultType } from "../pure/new-messages";
|
||||
import { QueryResultType } from "../query-server/new-messages";
|
||||
import { file } from "tmp-promise";
|
||||
import { writeFile } from "fs-extra";
|
||||
import { dump } from "js-yaml";
|
||||
|
||||
@@ -75,6 +75,16 @@ class ExternalApi extends DotNet::Callable {
|
||||
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.
|
||||
*/
|
||||
@@ -85,8 +95,7 @@ class ExternalApi extends DotNet::Callable {
|
||||
* Gets the namespace and signature of this API.
|
||||
*/
|
||||
bindingset[this]
|
||||
string getApiName() { result = this.getNamespace() + "." + this.getDeclaringType().getUnboundDeclaration() + "#" + this.getName() + "(" +
|
||||
parameterQualifiedTypeNamesToString(this) + ")" }
|
||||
string getApiName() { result = this.getNamespace() + "#" + this.getSignature() }
|
||||
|
||||
/** Gets a node that is an input to a call to this API. */
|
||||
private ArgumentNode getAnInput() {
|
||||
@@ -146,7 +155,7 @@ class ExternalApi extends DotNet::Callable {
|
||||
int resultLimit() { result = 1000 }
|
||||
|
||||
/**
|
||||
* Holds if it is relevant to count usages of "api".
|
||||
* Holds if it is relevant to count usages of \`api\`.
|
||||
*/
|
||||
signature predicate relevantApi(ExternalApi api);
|
||||
|
||||
@@ -174,7 +183,7 @@ module Results<relevantApi/1 getRelevantUsages> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there exists an API with "apiName" that is being used "usages" times
|
||||
* 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) {
|
||||
|
||||
@@ -41,31 +41,7 @@ private import semmle.code.java.dataflow.FlowSummary
|
||||
private import semmle.code.java.dataflow.internal.DataFlowPrivate
|
||||
private import semmle.code.java.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
|
||||
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"
|
||||
}
|
||||
private import semmle.code.java.dataflow.internal.ModelExclusions
|
||||
|
||||
/** Holds if the given callable is not worth supporting. */
|
||||
private predicate isUninteresting(Callable c) {
|
||||
@@ -88,10 +64,18 @@ class ExternalApi extends Callable {
|
||||
"#" + this.getName() + paramsString(this)
|
||||
}
|
||||
|
||||
private string getJarName() {
|
||||
result = this.getCompilationUnit().getParentContainer*().(JarFile).getBaseName()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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*()) }
|
||||
string jarContainer() {
|
||||
result = this.getJarName()
|
||||
or
|
||||
not exists(this.getJarName()) and result = "rt.jar"
|
||||
}
|
||||
|
||||
/** Gets a node that is an input to a call to this API. */
|
||||
private DataFlow::Node getAnInput() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DebugProtocol } from "@vscode/debugprotocol";
|
||||
import { QueryResultType } from "../pure/new-messages";
|
||||
import { QueryResultType } from "../query-server/new-messages";
|
||||
import { QuickEvalContext } from "../run-queries-shared";
|
||||
|
||||
// Events
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Disposable } from "vscode";
|
||||
import { CancellationTokenSource } from "vscode-jsonrpc";
|
||||
import { BaseLogger, LogOptions } from "../common/logging";
|
||||
import { queryServerLogger } from "../common/logging/vscode";
|
||||
import { QueryResultType } from "../pure/new-messages";
|
||||
import { QueryResultType } from "../query-server/new-messages";
|
||||
import { CoreQueryResults, CoreQueryRun, QueryRunner } from "../query-server";
|
||||
import * as CodeQLProtocol from "./debug-protocol";
|
||||
import { QuickEvalContext } from "../run-queries-shared";
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
import { CancellationToken, LocationLink, Uri } from "vscode";
|
||||
import { QueryOutputDir } from "../../run-queries-shared";
|
||||
import { QueryRunner } from "../../query-server";
|
||||
import { QueryResultType } from "../../pure/new-messages";
|
||||
import { QueryResultType } from "../../query-server/new-messages";
|
||||
import { fileRangeFromURI } from "./file-range-from-uri";
|
||||
|
||||
export const SELECT_QUERY_NAME = "#select";
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import { CompletedLocalQueryInfo, LocalQueryInfo } from "../query-results";
|
||||
import { WebviewReveal } from "./webview";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { QueryResultType } from "../pure/new-messages";
|
||||
import { QueryResultType } from "../query-server/new-messages";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { LocalQueries } from "./local-queries";
|
||||
import { tryGetQueryMetadata } from "../codeql-cli/query-metadata";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChildEvalLogTreeItem, EvalLogTreeItem } from "./eval-log-viewer";
|
||||
import { EvalLogData as EvalLogData } from "../pure/log-summary-parser";
|
||||
import { EvalLogData as EvalLogData } from "../log-insights/log-summary-parser";
|
||||
|
||||
/** Builds the tree data for the evaluator log viewer for a single query run. */
|
||||
export class EvalLogTreeBuilder {
|
||||
|
||||
@@ -40,7 +40,10 @@ import { CliVersionConstraint } from "../codeql-cli/cli";
|
||||
import { HistoryItemLabelProvider } from "./history-item-label-provider";
|
||||
import { ResultsView, WebviewReveal } from "../local-queries";
|
||||
import { EvalLogTreeBuilder, EvalLogViewer } from "../query-evaluation-logging";
|
||||
import { EvalLogData, parseViewerData } from "../pure/log-summary-parser";
|
||||
import {
|
||||
EvalLogData,
|
||||
parseViewerData,
|
||||
} from "../log-insights/log-summary-parser";
|
||||
import { QueryWithResults } from "../run-queries-shared";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { VariantAnalysisManager } from "../variant-analysis/variant-analysis-manager";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CancellationTokenSource, env } from "vscode";
|
||||
|
||||
import * as messages from "./pure/messages-shared";
|
||||
import * as legacyMessages from "./pure/legacy-messages";
|
||||
import * as messages from "./query-server/messages-shared";
|
||||
import * as legacyMessages from "./query-server/legacy-messages";
|
||||
import * as cli from "./codeql-cli/cli";
|
||||
import { pathExists } from "fs-extra";
|
||||
import { basename } from "path";
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
Dataset,
|
||||
deregisterDatabases,
|
||||
registerDatabases,
|
||||
} from "../../pure/legacy-messages";
|
||||
} from "../legacy-messages";
|
||||
import {
|
||||
CoreQueryResults,
|
||||
CoreQueryTarget,
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
progress,
|
||||
ProgressMessage,
|
||||
WithProgressId,
|
||||
} from "../../pure/legacy-messages";
|
||||
} from "../legacy-messages";
|
||||
import { ProgressCallback, ProgressTask } from "../../common/vscode/progress";
|
||||
import { ServerProcess } from "../server-process";
|
||||
import { App } from "../../common/app";
|
||||
|
||||
@@ -18,15 +18,15 @@ import {
|
||||
showAndLogExceptionWithTelemetry,
|
||||
showAndLogWarningMessage,
|
||||
} from "../../common/logging";
|
||||
import * as messages from "../../pure/legacy-messages";
|
||||
import * as newMessages from "../../pure/new-messages";
|
||||
import * as messages from "../legacy-messages";
|
||||
import * as newMessages from "../new-messages";
|
||||
import * as qsClient from "./query-server-client";
|
||||
import { asError, getErrorMessage } from "../../common/helpers-pure";
|
||||
import { compileDatabaseUpgradeSequence } from "./upgrades";
|
||||
import { QueryEvaluationInfo, QueryOutputDir } from "../../run-queries-shared";
|
||||
import { redactableError } from "../../common/errors";
|
||||
import { CoreQueryResults, CoreQueryTarget } from "../query-runner";
|
||||
import { Position } from "../../pure/messages-shared";
|
||||
import { Position } from "../messages-shared";
|
||||
import { ensureDirSync } from "fs-extra";
|
||||
import { telemetryListener } from "../../common/vscode/telemetry";
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "../../common/vscode/progress";
|
||||
import { extLogger } from "../../common/logging/vscode";
|
||||
import { showAndLogExceptionWithTelemetry } from "../../common/logging";
|
||||
import * as messages from "../../pure/legacy-messages";
|
||||
import * as messages from "../legacy-messages";
|
||||
import * as qsClient from "./query-server-client";
|
||||
import * as tmp from "tmp-promise";
|
||||
import { dirname } from "path";
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
deregisterDatabases,
|
||||
registerDatabases,
|
||||
upgradeDatabase,
|
||||
} from "../pure/new-messages";
|
||||
} from "./new-messages";
|
||||
import { CoreQueryResults, CoreQueryTarget, QueryRunner } from "./query-runner";
|
||||
import { QueryServerClient } from "./query-server-client";
|
||||
import { compileAndRunQueryAgainstDatabaseCore } from "./run-queries";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { ProgressCallback } from "../common/vscode/progress";
|
||||
import { DatabaseItem } from "../databases/local-databases";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
import { Position, QueryResultType } from "../pure/new-messages";
|
||||
import { Position, QueryResultType } from "./new-messages";
|
||||
import { BaseLogger, Logger } from "../common/logging";
|
||||
import { basename, join } from "path";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
@@ -7,11 +7,7 @@ import * as cli from "../codeql-cli/cli";
|
||||
import { QueryServerConfig } from "../config";
|
||||
import { Logger, showAndLogErrorMessage } from "../common/logging";
|
||||
import { extLogger, ProgressReporter } from "../common/logging/vscode";
|
||||
import {
|
||||
progress,
|
||||
ProgressMessage,
|
||||
WithProgressId,
|
||||
} from "../pure/new-messages";
|
||||
import { progress, ProgressMessage, WithProgressId } from "./new-messages";
|
||||
import {
|
||||
ProgressCallback,
|
||||
ProgressTask,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CancellationToken } from "vscode";
|
||||
import { ProgressCallback } from "../common/vscode/progress";
|
||||
import * as messages from "../pure/new-messages";
|
||||
import * as messages from "./new-messages";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
import * as qsClient from "./query-server-client";
|
||||
import { CoreQueryResults, CoreQueryTarget } from "./query-runner";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as messages from "./pure/messages-shared";
|
||||
import * as legacyMessages from "./pure/legacy-messages";
|
||||
import * as messages from "./query-server/messages-shared";
|
||||
import * as legacyMessages from "./query-server/legacy-messages";
|
||||
import { DatabaseInfo, QueryMetadata } from "./common/interface-types";
|
||||
import { join, parse, dirname, basename } from "path";
|
||||
import { Range, TextEditor, Uri, window, workspace } from "vscode";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useState } from "react";
|
||||
import { ComponentMeta } from "@storybook/react";
|
||||
|
||||
import { RepositoriesFilter as RepositoriesFilterComponent } from "../../view/variant-analysis/RepositoriesFilter";
|
||||
import { FilterKey } from "../../pure/variant-analysis-filter-sort";
|
||||
import { FilterKey } from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
|
||||
export default {
|
||||
title: "Variant Analysis/Repositories Filter",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useState } from "react";
|
||||
import { ComponentMeta } from "@storybook/react";
|
||||
|
||||
import { RepositoriesSearchSortRow as RepositoriesSearchSortRowComponent } from "../../view/variant-analysis/RepositoriesSearchSortRow";
|
||||
import { defaultFilterSortState } from "../../pure/variant-analysis-filter-sort";
|
||||
import { defaultFilterSortState } from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
|
||||
export default {
|
||||
title: "Variant Analysis/Repositories Search and Sort Row",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useState } from "react";
|
||||
import { ComponentMeta } from "@storybook/react";
|
||||
|
||||
import { RepositoriesSort as RepositoriesSortComponent } from "../../view/variant-analysis/RepositoriesSort";
|
||||
import { SortKey } from "../../pure/variant-analysis-filter-sort";
|
||||
import { SortKey } from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
|
||||
export default {
|
||||
title: "Variant Analysis/Repositories Sort",
|
||||
|
||||
@@ -15,7 +15,7 @@ import { createMockRepositoryWithMetadata } from "../../../test/factories/varian
|
||||
import {
|
||||
defaultFilterSortState,
|
||||
RepositoriesFilterSortState,
|
||||
} from "../../pure/variant-analysis-filter-sort";
|
||||
} from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
|
||||
export default {
|
||||
title: "Variant Analysis/Variant Analysis Outcome Panels",
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
import {
|
||||
filterAndSortRepositoriesWithResults,
|
||||
RepositoriesFilterSortStateWithIds,
|
||||
} from "../pure/variant-analysis-filter-sort";
|
||||
} from "./shared/variant-analysis-filter-sort";
|
||||
import { Credentials } from "../common/authentication";
|
||||
import { AppCommandManager } from "../common/commands";
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import {
|
||||
Repository,
|
||||
RepositoryWithMetadata,
|
||||
} from "../variant-analysis/shared/repository";
|
||||
import { parseDate } from "../common/date";
|
||||
import { assertNever } from "../common/helpers-pure";
|
||||
import { Repository, RepositoryWithMetadata } from "./repository";
|
||||
import { parseDate } from "../../common/date";
|
||||
import { assertNever } from "../../common/helpers-pure";
|
||||
|
||||
export enum FilterKey {
|
||||
All = "all",
|
||||
@@ -57,7 +57,7 @@ import {
|
||||
defaultFilterSortState,
|
||||
filterAndSortRepositoriesWithResults,
|
||||
RepositoriesFilterSortStateWithIds,
|
||||
} from "../pure/variant-analysis-filter-sort";
|
||||
} from "./shared/variant-analysis-filter-sort";
|
||||
import { URLSearchParams } from "url";
|
||||
import { DbManager } from "../databases/db-manager";
|
||||
import { App } from "../common/app";
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
VariantAnalysisScannedRepositoryState,
|
||||
} from "./shared/variant-analysis";
|
||||
import { AppCommandManager } from "../common/commands";
|
||||
import { RepositoriesFilterSortStateWithIds } from "../pure/variant-analysis-filter-sort";
|
||||
import { RepositoriesFilterSortStateWithIds } from "./shared/variant-analysis-filter-sort";
|
||||
|
||||
export interface VariantAnalysisViewInterface {
|
||||
variantAnalysisId: number;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { basename } from "path";
|
||||
import * as React from "react";
|
||||
import * as Sarif from "sarif";
|
||||
import * as Keys from "../../pure/result-keys";
|
||||
import * as Keys from "./result-keys";
|
||||
import { chevronDown, chevronRight, info, listUnordered } from "./octicons";
|
||||
import {
|
||||
className,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useCallback } from "react";
|
||||
import styled from "styled-components";
|
||||
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react";
|
||||
import { Codicon } from "../common";
|
||||
import { FilterKey } from "../../pure/variant-analysis-filter-sort";
|
||||
import { FilterKey } from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
|
||||
const Dropdown = styled(VSCodeDropdown)`
|
||||
width: 100%;
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
FilterKey,
|
||||
RepositoriesFilterSortState,
|
||||
SortKey,
|
||||
} from "../../pure/variant-analysis-filter-sort";
|
||||
} from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import { RepositoriesSearch } from "./RepositoriesSearch";
|
||||
import { RepositoriesSort } from "./RepositoriesSort";
|
||||
import { RepositoriesFilter } from "./RepositoriesFilter";
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as React from "react";
|
||||
import { useCallback } from "react";
|
||||
import styled from "styled-components";
|
||||
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react";
|
||||
import { SortKey } from "../../pure/variant-analysis-filter-sort";
|
||||
import { SortKey } from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import { Codicon } from "../common";
|
||||
|
||||
const Dropdown = styled(VSCodeDropdown)`
|
||||
|
||||
@@ -12,7 +12,7 @@ import { VariantAnalysisOutcomePanels } from "./VariantAnalysisOutcomePanels";
|
||||
import { VariantAnalysisLoading } from "./VariantAnalysisLoading";
|
||||
import { ToVariantAnalysisMessage } from "../../common/interface-types";
|
||||
import { vscode } from "../vscode-api";
|
||||
import { defaultFilterSortState } from "../../pure/variant-analysis-filter-sort";
|
||||
import { defaultFilterSortState } from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import { sendTelemetry, useTelemetryOnChange } from "../common/telemetry";
|
||||
|
||||
export type VariantAnalysisProps = {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import {
|
||||
filterAndSortRepositoriesWithResultsByName,
|
||||
RepositoriesFilterSortState,
|
||||
} from "../../pure/variant-analysis-filter-sort";
|
||||
} from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
defaultFilterSortState,
|
||||
filterAndSortRepositoriesWithResults,
|
||||
RepositoriesFilterSortState,
|
||||
} from "../../pure/variant-analysis-filter-sort";
|
||||
} from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
|
||||
export type VariantAnalysisHeaderProps = {
|
||||
variantAnalysis: VariantAnalysis;
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { VariantAnalysisAnalyzedRepos } from "./VariantAnalysisAnalyzedRepos";
|
||||
import { Alert } from "../common";
|
||||
import { VariantAnalysisSkippedRepositoriesTab } from "./VariantAnalysisSkippedRepositoriesTab";
|
||||
import { RepositoriesFilterSortState } from "../../pure/variant-analysis-filter-sort";
|
||||
import { RepositoriesFilterSortState } from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import { RepositoriesSearchSortRow } from "./RepositoriesSearchSortRow";
|
||||
import { FailureReasonAlert } from "./FailureReasonAlert";
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
compareRepository,
|
||||
matchesFilter,
|
||||
RepositoriesFilterSortState,
|
||||
} from "../../pure/variant-analysis-filter-sort";
|
||||
} from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
|
||||
export type VariantAnalysisSkippedRepositoriesTabProps = {
|
||||
alertTitle: string;
|
||||
|
||||
@@ -7,7 +7,10 @@ import {
|
||||
import { VariantAnalysis, VariantAnalysisProps } from "../VariantAnalysis";
|
||||
import { createMockVariantAnalysis } from "../../../../test/factories/variant-analysis/shared/variant-analysis";
|
||||
import { ToVariantAnalysisMessage } from "../../../common/interface-types";
|
||||
import { FilterKey, SortKey } from "../../../pure/variant-analysis-filter-sort";
|
||||
import {
|
||||
FilterKey,
|
||||
SortKey,
|
||||
} from "../../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import { postMessage } from "../../common/post-message";
|
||||
|
||||
describe(VariantAnalysis.name, () => {
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { createMockVariantAnalysis } from "../../../../test/factories/variant-analysis/shared/variant-analysis";
|
||||
import { createMockRepositoryWithMetadata } from "../../../../test/factories/variant-analysis/shared/repository";
|
||||
import { createMockScannedRepo } from "../../../../test/factories/variant-analysis/shared/scanned-repositories";
|
||||
import { SortKey } from "../../../pure/variant-analysis-filter-sort";
|
||||
import { SortKey } from "../../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import { permissiveFilterSortState } from "../../../../test/unit-tests/variant-analysis-filter-sort.test";
|
||||
|
||||
describe(VariantAnalysisAnalyzedRepos.name, () => {
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
createMockScannedRepo,
|
||||
createMockScannedRepos,
|
||||
} from "../../../../test/factories/variant-analysis/shared/scanned-repositories";
|
||||
import { defaultFilterSortState } from "../../../pure/variant-analysis-filter-sort";
|
||||
import { defaultFilterSortState } from "../../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
|
||||
describe(VariantAnalysisOutcomePanels.name, () => {
|
||||
const defaultVariantAnalysis = {
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
VariantAnalysisSkippedRepositoriesTab,
|
||||
VariantAnalysisSkippedRepositoriesTabProps,
|
||||
} from "../VariantAnalysisSkippedRepositoriesTab";
|
||||
import { SortKey } from "../../../pure/variant-analysis-filter-sort";
|
||||
import { SortKey } from "../../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import { permissiveFilterSortState } from "../../../../test/unit-tests/variant-analysis-filter-sort.test";
|
||||
|
||||
describe(VariantAnalysisSkippedRepositoriesTab.name, () => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
QueryWithResults,
|
||||
} from "../../../src/run-queries-shared";
|
||||
import { CancellationTokenSource } from "vscode";
|
||||
import { QueryResultType } from "../../../src/pure/legacy-messages";
|
||||
import { QueryResultType } from "../../../src/query-server/legacy-messages";
|
||||
import { QueryMetadata } from "../../../src/common/interface-types";
|
||||
|
||||
export function createMockLocalQueryInfo({
|
||||
|
||||
@@ -49,7 +49,7 @@ describe("commands declared in package.json", () => {
|
||||
expect(title).toBeDefined();
|
||||
commandTitles[command] = title!;
|
||||
} else {
|
||||
fail(`Unexpected command name ${command}`);
|
||||
throw new Error(`Unexpected command name ${command}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ describe("helpers-pure", () => {
|
||||
|
||||
try {
|
||||
await asyncFilter([1, 2, 3], rejects);
|
||||
fail("Should have thrown");
|
||||
throw new Error("Should have thrown");
|
||||
} catch (e) {
|
||||
expect(getErrorMessage(e)).toBe("opps");
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
autoNameExtensionPack,
|
||||
formatPackName,
|
||||
parsePackName,
|
||||
validatePackName,
|
||||
} from "../../../src/data-extensions-editor/extension-pack-name";
|
||||
|
||||
describe("autoNameExtensionPack", () => {
|
||||
const testCases: Array<{
|
||||
name: string;
|
||||
language: string;
|
||||
expected: string;
|
||||
}> = [
|
||||
{
|
||||
name: "github/vscode-codeql",
|
||||
language: "javascript",
|
||||
expected: "github/vscode-codeql-javascript",
|
||||
},
|
||||
{
|
||||
name: "vscode-codeql",
|
||||
language: "a",
|
||||
expected: "pack/vscode-codeql-a",
|
||||
},
|
||||
{
|
||||
name: "b",
|
||||
language: "java",
|
||||
expected: "pack/b-java",
|
||||
},
|
||||
{
|
||||
name: "a/b",
|
||||
language: "csharp",
|
||||
expected: "a/b-csharp",
|
||||
},
|
||||
{
|
||||
name: "-/b",
|
||||
language: "csharp",
|
||||
expected: "pack/b-csharp",
|
||||
},
|
||||
{
|
||||
name: "a/b/c/d",
|
||||
language: "csharp",
|
||||
expected: "a/b-c-d-csharp",
|
||||
},
|
||||
{
|
||||
name: "JAVA/CodeQL",
|
||||
language: "csharp",
|
||||
expected: "java/codeql-csharp",
|
||||
},
|
||||
{
|
||||
name: "my new pack",
|
||||
language: "swift",
|
||||
expected: "pack/my-new-pack-swift",
|
||||
},
|
||||
{
|
||||
name: "gïthub/vscode-codeql",
|
||||
language: "javascript",
|
||||
expected: "gthub/vscode-codeql-javascript",
|
||||
},
|
||||
{
|
||||
name: "a/b-",
|
||||
language: "csharp",
|
||||
expected: "a/b-csharp",
|
||||
},
|
||||
{
|
||||
name: "-a-/b",
|
||||
language: "ruby",
|
||||
expected: "a/b-ruby",
|
||||
},
|
||||
{
|
||||
name: "a/b--d--e-d-",
|
||||
language: "csharp",
|
||||
expected: "a/b-d-e-d-csharp",
|
||||
},
|
||||
];
|
||||
|
||||
test.each(testCases)(
|
||||
"$name with $language = $expected",
|
||||
({ name, language, expected }) => {
|
||||
const result = autoNameExtensionPack(name, language);
|
||||
expect(result).not.toBeUndefined();
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
expect(validatePackName(formatPackName(result))).toBeUndefined();
|
||||
expect(result).toEqual(parsePackName(expected));
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { join } from "path";
|
||||
|
||||
import { parseViewerData } from "../../../src/pure/log-summary-parser";
|
||||
import { parseViewerData } from "../../../src/log-insights/log-summary-parser";
|
||||
|
||||
describe("Evaluator log summary tests", () => {
|
||||
describe("for a valid summary text", () => {
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
FilterKey,
|
||||
matchesFilter,
|
||||
SortKey,
|
||||
} from "../../src/pure/variant-analysis-filter-sort";
|
||||
} from "../../src/variant-analysis/shared/variant-analysis-filter-sort";
|
||||
|
||||
/** A filterSortState that matches everything */
|
||||
export const permissiveFilterSortState = {
|
||||
|
||||
@@ -36,7 +36,7 @@ import {
|
||||
import { createTimestampFile } from "../../../../src/run-queries-shared";
|
||||
import { createMockVariantAnalysisRepoTask } from "../../../factories/variant-analysis/gh-api/variant-analysis-repo-task";
|
||||
import { VariantAnalysisRepoTask } from "../../../../src/variant-analysis/gh-api/variant-analysis";
|
||||
import { SortKey } from "../../../../src/pure/variant-analysis-filter-sort";
|
||||
import { SortKey } from "../../../../src/variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import { DbManager } from "../../../../src/databases/db-manager";
|
||||
import { App } from "../../../../src/common/app";
|
||||
import { ExtensionApp } from "../../../../src/common/vscode/vscode-app";
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from "vscode";
|
||||
import * as CodeQLProtocol from "../../../../src/debugger/debug-protocol";
|
||||
import { DisposableObject } from "../../../../src/common/disposable-object";
|
||||
import { QueryResultType } from "../../../../src/pure/legacy-messages";
|
||||
import { QueryResultType } from "../../../../src/query-server/legacy-messages";
|
||||
import { CoreCompletedQuery } from "../../../../src/query-server/query-runner";
|
||||
import { QueryOutputDir } from "../../../../src/run-queries-shared";
|
||||
import {
|
||||
@@ -310,11 +310,7 @@ export class DebugController
|
||||
*/
|
||||
private async nextEvent(): Promise<AnyDebugEvent> {
|
||||
if (this.resolver !== undefined) {
|
||||
const error = new Error(
|
||||
"Attempt to wait for multiple debugger events at once.",
|
||||
);
|
||||
fail(error);
|
||||
throw error;
|
||||
throw new Error("Attempt to wait for multiple debugger events at once.");
|
||||
} else {
|
||||
if (this.nextEventIndex < this.eventQueue.length) {
|
||||
// No need to wait.
|
||||
|
||||
@@ -3,7 +3,7 @@ import { join, basename } from "path";
|
||||
import { dirSync } from "tmp";
|
||||
import { pathToFileURL } from "url";
|
||||
import { CancellationTokenSource } from "vscode-jsonrpc";
|
||||
import * as messages from "../../../src/pure/legacy-messages";
|
||||
import * as messages from "../../../src/query-server/legacy-messages";
|
||||
import * as qsClient from "../../../src/query-server/legacy/query-server-client";
|
||||
import * as cli from "../../../src/codeql-cli/cli";
|
||||
import { CellValue } from "../../../src/common/bqrs-cli-types";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { join, basename } from "path";
|
||||
import { dirSync } from "tmp";
|
||||
import { CancellationTokenSource } from "vscode-jsonrpc";
|
||||
import * as messages from "../../../src/pure/new-messages";
|
||||
import * as messages from "../../../src/query-server/new-messages";
|
||||
import * as qsClient from "../../../src/query-server/query-server-client";
|
||||
import * as cli from "../../../src/codeql-cli/cli";
|
||||
import { CellValue } from "../../../src/common/bqrs-cli-types";
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
extLogger,
|
||||
ProgressReporter,
|
||||
} from "../../../src/common/logging/vscode";
|
||||
import { QueryResultType } from "../../../src/pure/new-messages";
|
||||
import { QueryResultType } from "../../../src/query-server/new-messages";
|
||||
import { ensureTestDatabase, getActivatedExtension } from "../global.helper";
|
||||
import { createMockApp } from "../../__mocks__/appMock";
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
} from "../../../src/query-server/query-runner";
|
||||
import { SELECT_QUERY_NAME } from "../../../src/language-support";
|
||||
import { LocalQueries, QuickEvalType } from "../../../src/local-queries";
|
||||
import { QueryResultType } from "../../../src/pure/new-messages";
|
||||
import { QueryResultType } from "../../../src/query-server/new-messages";
|
||||
import { createVSCodeCommandManager } from "../../../src/common/vscode/commands";
|
||||
import {
|
||||
AllCommands,
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { CancellationTokenSource, QuickPickItem, window } from "vscode";
|
||||
import {
|
||||
CancellationTokenSource,
|
||||
QuickPickItem,
|
||||
Uri,
|
||||
window,
|
||||
workspace,
|
||||
WorkspaceFolder,
|
||||
} from "vscode";
|
||||
import { dump as dumpYaml, load as loadYaml } from "js-yaml";
|
||||
import { outputFile, readFile } from "fs-extra";
|
||||
import { join } from "path";
|
||||
@@ -8,6 +15,8 @@ import {
|
||||
ResolveExtensionsResult,
|
||||
} from "../../../../src/codeql-cli/cli";
|
||||
|
||||
import * as config from "../../../../src/config";
|
||||
|
||||
import { pickExtensionPackModelFile } from "../../../../src/data-extensions-editor/extension-pack-picker";
|
||||
import { ExtensionPack } from "../../../../src/data-extensions-editor/shared/extension-pack";
|
||||
import { createMockLogger } from "../../../__mocks__/loggerMock";
|
||||
@@ -16,8 +25,10 @@ describe("pickExtensionPackModelFile", () => {
|
||||
let tmpDir: string;
|
||||
let extensionPackPath: string;
|
||||
let anotherExtensionPackPath: string;
|
||||
let autoExtensionPackPath: string;
|
||||
let extensionPack: ExtensionPack;
|
||||
let anotherExtensionPack: ExtensionPack;
|
||||
let autoExtensionPack: ExtensionPack;
|
||||
|
||||
let qlPacks: QlpacksInfo;
|
||||
let extensions: ResolveExtensionsResult;
|
||||
@@ -32,6 +43,12 @@ describe("pickExtensionPackModelFile", () => {
|
||||
const progress = jest.fn();
|
||||
let showQuickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
|
||||
let showInputBoxSpy: jest.SpiedFunction<typeof window.showInputBox>;
|
||||
let disableAutoNameExtensionPackSpy: jest.SpiedFunction<
|
||||
typeof config.disableAutoNameExtensionPack
|
||||
>;
|
||||
let workspaceFoldersSpy: jest.SpyInstance;
|
||||
let additionalPacks: string[];
|
||||
let workspaceFolder: WorkspaceFolder;
|
||||
|
||||
const logger = createMockLogger();
|
||||
|
||||
@@ -42,12 +59,17 @@ describe("pickExtensionPackModelFile", () => {
|
||||
})
|
||||
).path;
|
||||
|
||||
extensionPackPath = join(tmpDir, "my-extension-pack");
|
||||
anotherExtensionPackPath = join(tmpDir, "another-extension-pack");
|
||||
// Uri.file(...).fsPath normalizes the filenames so we can properly compare them on Windows
|
||||
extensionPackPath = Uri.file(join(tmpDir, "my-extension-pack")).fsPath;
|
||||
anotherExtensionPackPath = Uri.file(
|
||||
join(tmpDir, "another-extension-pack"),
|
||||
).fsPath;
|
||||
autoExtensionPackPath = Uri.file(join(tmpDir, "vscode-codeql-java")).fsPath;
|
||||
|
||||
qlPacks = {
|
||||
"my-extension-pack": [extensionPackPath],
|
||||
"another-extension-pack": [anotherExtensionPackPath],
|
||||
"github/vscode-codeql-java": [autoExtensionPackPath],
|
||||
};
|
||||
extensions = {
|
||||
models: [],
|
||||
@@ -59,6 +81,13 @@ describe("pickExtensionPackModelFile", () => {
|
||||
predicate: "sinkModel",
|
||||
},
|
||||
],
|
||||
[autoExtensionPackPath]: [
|
||||
{
|
||||
file: join(autoExtensionPackPath, "models", "model.yml"),
|
||||
index: 0,
|
||||
predicate: "sinkModel",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -70,6 +99,10 @@ describe("pickExtensionPackModelFile", () => {
|
||||
anotherExtensionPackPath,
|
||||
"another-extension-pack",
|
||||
);
|
||||
autoExtensionPack = await createMockExtensionPack(
|
||||
autoExtensionPackPath,
|
||||
"github/vscode-codeql-java",
|
||||
);
|
||||
|
||||
showQuickPickSpy = jest
|
||||
.spyOn(window, "showQuickPick")
|
||||
@@ -77,6 +110,19 @@ describe("pickExtensionPackModelFile", () => {
|
||||
showInputBoxSpy = jest
|
||||
.spyOn(window, "showInputBox")
|
||||
.mockRejectedValue(new Error("Unexpected call to showInputBox"));
|
||||
disableAutoNameExtensionPackSpy = jest
|
||||
.spyOn(config, "disableAutoNameExtensionPack")
|
||||
.mockReturnValue(true);
|
||||
|
||||
workspaceFolder = {
|
||||
uri: Uri.file(tmpDir),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 0,
|
||||
};
|
||||
additionalPacks = [Uri.file(tmpDir).fsPath];
|
||||
workspaceFoldersSpy = jest
|
||||
.spyOn(workspace, "workspaceFolders", "get")
|
||||
.mockReturnValue([workspaceFolder]);
|
||||
});
|
||||
|
||||
it("allows choosing an existing extension pack and model file", async () => {
|
||||
@@ -120,6 +166,12 @@ describe("pickExtensionPackModelFile", () => {
|
||||
detail: anotherExtensionPackPath,
|
||||
extensionPack: anotherExtensionPack,
|
||||
},
|
||||
{
|
||||
label: "github/vscode-codeql-java",
|
||||
description: "0.0.0",
|
||||
detail: autoExtensionPackPath,
|
||||
extensionPack: autoExtensionPack,
|
||||
},
|
||||
{
|
||||
label: expect.stringMatching(/create/i),
|
||||
extensionPack: null,
|
||||
@@ -147,11 +199,14 @@ describe("pickExtensionPackModelFile", () => {
|
||||
token,
|
||||
);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledTimes(1);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledWith([], true);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledWith(
|
||||
additionalPacks,
|
||||
true,
|
||||
);
|
||||
expect(cliServer.resolveExtensions).toHaveBeenCalledTimes(1);
|
||||
expect(cliServer.resolveExtensions).toHaveBeenCalledWith(
|
||||
extensionPackPath,
|
||||
[],
|
||||
additionalPacks,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -190,14 +245,160 @@ describe("pickExtensionPackModelFile", () => {
|
||||
token,
|
||||
);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledTimes(1);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledWith([], true);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledWith(
|
||||
additionalPacks,
|
||||
true,
|
||||
);
|
||||
expect(cliServer.resolveExtensions).toHaveBeenCalledTimes(1);
|
||||
expect(cliServer.resolveExtensions).toHaveBeenCalledWith(
|
||||
extensionPackPath,
|
||||
[],
|
||||
additionalPacks,
|
||||
);
|
||||
});
|
||||
|
||||
it("automatically selects an extension pack and allows selecting an existing model file", async () => {
|
||||
disableAutoNameExtensionPackSpy.mockReturnValue(false);
|
||||
|
||||
const modelPath = join(autoExtensionPackPath, "models", "model.yml");
|
||||
|
||||
const cliServer = mockCliServer(qlPacks, extensions);
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "models/model.yml",
|
||||
file: modelPath,
|
||||
} as QuickPickItem);
|
||||
|
||||
expect(
|
||||
await pickExtensionPackModelFile(
|
||||
cliServer,
|
||||
databaseItem,
|
||||
logger,
|
||||
progress,
|
||||
token,
|
||||
),
|
||||
).toEqual({
|
||||
filename: modelPath,
|
||||
extensionPack: autoExtensionPack,
|
||||
});
|
||||
expect(showQuickPickSpy).toHaveBeenCalledTimes(1);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
{
|
||||
label: "models/model.yml",
|
||||
file: modelPath,
|
||||
},
|
||||
{
|
||||
label: expect.stringMatching(/create/i),
|
||||
file: null,
|
||||
},
|
||||
],
|
||||
{
|
||||
title: expect.any(String),
|
||||
},
|
||||
token,
|
||||
);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledTimes(1);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledWith(
|
||||
additionalPacks,
|
||||
true,
|
||||
);
|
||||
expect(cliServer.resolveExtensions).toHaveBeenCalledTimes(1);
|
||||
expect(cliServer.resolveExtensions).toHaveBeenCalledWith(
|
||||
autoExtensionPackPath,
|
||||
additionalPacks,
|
||||
);
|
||||
});
|
||||
|
||||
it("automatically creates an extension pack and allows creating a new model file", async () => {
|
||||
disableAutoNameExtensionPackSpy.mockReturnValue(false);
|
||||
|
||||
const tmpDir = await dir({
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
|
||||
workspaceFoldersSpy.mockReturnValue([
|
||||
{
|
||||
uri: Uri.file("/b/a/c"),
|
||||
name: "my-workspace",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
uri: Uri.file("/a/b/c"),
|
||||
name: "codeql-custom-queries-csharp",
|
||||
index: 1,
|
||||
},
|
||||
{
|
||||
uri: Uri.joinPath(Uri.file(tmpDir.path), "codeql-custom-queries-java"),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 2,
|
||||
},
|
||||
]);
|
||||
jest
|
||||
.spyOn(workspace, "workspaceFile", "get")
|
||||
.mockReturnValue(
|
||||
Uri.joinPath(Uri.file(tmpDir.path), "workspace.code-workspace"),
|
||||
);
|
||||
jest.spyOn(workspace, "updateWorkspaceFolders").mockReturnValue(true);
|
||||
|
||||
const newPackDir = join(
|
||||
Uri.file(tmpDir.path).fsPath,
|
||||
".github",
|
||||
"codeql",
|
||||
"extensions",
|
||||
"vscode-codeql-java",
|
||||
);
|
||||
|
||||
const cliServer = mockCliServer({}, { models: [], data: {} });
|
||||
|
||||
showInputBoxSpy.mockResolvedValue("models/my-model.yml");
|
||||
|
||||
expect(
|
||||
await pickExtensionPackModelFile(
|
||||
cliServer,
|
||||
databaseItem,
|
||||
logger,
|
||||
progress,
|
||||
token,
|
||||
),
|
||||
).toEqual({
|
||||
filename: join(newPackDir, "models", "my-model.yml"),
|
||||
extensionPack: {
|
||||
path: newPackDir,
|
||||
yamlPath: join(newPackDir, "codeql-pack.yml"),
|
||||
name: "github/vscode-codeql-java",
|
||||
version: "0.0.0",
|
||||
extensionTargets: {
|
||||
"codeql/java-all": "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml"],
|
||||
},
|
||||
});
|
||||
expect(showQuickPickSpy).not.toHaveBeenCalled();
|
||||
expect(showInputBoxSpy).toHaveBeenCalledTimes(1);
|
||||
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(newPackDir, "codeql-pack.yml"), "utf8")),
|
||||
).toEqual({
|
||||
name: "github/vscode-codeql-java",
|
||||
version: "0.0.0",
|
||||
library: true,
|
||||
extensionTargets: {
|
||||
"codeql/java-all": "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml"],
|
||||
});
|
||||
});
|
||||
|
||||
it("allows cancelling the extension pack prompt", async () => {
|
||||
const cliServer = mockCliServer(qlPacks, extensions);
|
||||
|
||||
@@ -223,11 +424,15 @@ describe("pickExtensionPackModelFile", () => {
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
|
||||
const newPackDir = join(tmpDir.path, "new-extension-pack");
|
||||
const newPackDir = join(Uri.file(tmpDir.path).fsPath, "new-extension-pack");
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "codeql-custom-queries-java",
|
||||
path: tmpDir.path,
|
||||
folder: {
|
||||
uri: Uri.file(tmpDir.path),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 0,
|
||||
},
|
||||
} as QuickPickItem);
|
||||
showInputBoxSpy.mockResolvedValueOnce("pack/new-extension-pack");
|
||||
showInputBoxSpy.mockResolvedValue("models/my-model.yml");
|
||||
@@ -259,7 +464,7 @@ describe("pickExtensionPackModelFile", () => {
|
||||
{
|
||||
title: expect.stringMatching(/extension pack/i),
|
||||
prompt: expect.stringMatching(/extension pack/i),
|
||||
placeHolder: expect.stringMatching(/github\/vscode-codeql-extensions/),
|
||||
placeHolder: expect.stringMatching(/github\/vscode-codeql-java/),
|
||||
validateInput: expect.any(Function),
|
||||
},
|
||||
token,
|
||||
@@ -295,11 +500,15 @@ describe("pickExtensionPackModelFile", () => {
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
|
||||
const newPackDir = join(tmpDir.path, "new-extension-pack");
|
||||
const newPackDir = join(Uri.file(tmpDir.path).fsPath, "new-extension-pack");
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "codeql-custom-queries-java",
|
||||
path: tmpDir.path,
|
||||
folder: {
|
||||
uri: Uri.file(tmpDir.path),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 0,
|
||||
},
|
||||
} as QuickPickItem);
|
||||
showInputBoxSpy.mockResolvedValueOnce("pack/new-extension-pack");
|
||||
showInputBoxSpy.mockResolvedValue("models/my-model.yml");
|
||||
@@ -334,7 +543,7 @@ describe("pickExtensionPackModelFile", () => {
|
||||
{
|
||||
title: expect.stringMatching(/extension pack/i),
|
||||
prompt: expect.stringMatching(/extension pack/i),
|
||||
placeHolder: expect.stringMatching(/github\/vscode-codeql-extensions/),
|
||||
placeHolder: expect.stringMatching(/github\/vscode-codeql-csharp/),
|
||||
validateInput: expect.any(Function),
|
||||
},
|
||||
token,
|
||||
@@ -388,7 +597,11 @@ describe("pickExtensionPackModelFile", () => {
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "codeql-custom-queries-java",
|
||||
path: "/a/b/c",
|
||||
folder: {
|
||||
uri: Uri.file("/a/b/c"),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 0,
|
||||
},
|
||||
} as QuickPickItem);
|
||||
showInputBoxSpy.mockResolvedValueOnce(undefined);
|
||||
|
||||
@@ -777,7 +990,11 @@ describe("pickExtensionPackModelFile", () => {
|
||||
|
||||
showQuickPickSpy.mockResolvedValueOnce({
|
||||
label: "a",
|
||||
path: "/a/b/c",
|
||||
folder: {
|
||||
uri: Uri.file("/a/b/c"),
|
||||
name: "a",
|
||||
index: 0,
|
||||
},
|
||||
} as QuickPickItem);
|
||||
showInputBoxSpy.mockResolvedValue(undefined);
|
||||
|
||||
@@ -1014,7 +1231,10 @@ describe("pickExtensionPackModelFile", () => {
|
||||
token,
|
||||
);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledTimes(1);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledWith([], true);
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalledWith(
|
||||
additionalPacks,
|
||||
true,
|
||||
);
|
||||
expect(cliServer.resolveExtensions).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
import { Uri, workspace, WorkspaceFolder } from "vscode";
|
||||
import { dir, DirectoryResult } from "tmp-promise";
|
||||
import { join } from "path";
|
||||
import { autoPickExtensionsDirectory } from "../../../../src/data-extensions-editor/extensions-workspace-folder";
|
||||
import * as files from "../../../../src/common/files";
|
||||
import { mkdirp } from "fs-extra";
|
||||
|
||||
describe("autoPickExtensionsDirectory", () => {
|
||||
let tmpDir: DirectoryResult;
|
||||
let rootDirectory: Uri;
|
||||
let extensionsDirectory: Uri;
|
||||
|
||||
let workspaceFoldersSpy: jest.SpyInstance<
|
||||
readonly WorkspaceFolder[] | undefined,
|
||||
[]
|
||||
>;
|
||||
let workspaceFileSpy: jest.SpyInstance<Uri | undefined, []>;
|
||||
let updateWorkspaceFoldersSpy: jest.SpiedFunction<
|
||||
typeof workspace.updateWorkspaceFolders
|
||||
>;
|
||||
let mockedTmpDirUri: Uri;
|
||||
|
||||
beforeEach(async () => {
|
||||
tmpDir = await dir({
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
|
||||
rootDirectory = Uri.joinPath(Uri.file(tmpDir.path), "root");
|
||||
extensionsDirectory = Uri.joinPath(
|
||||
rootDirectory,
|
||||
".github",
|
||||
"codeql",
|
||||
"extensions",
|
||||
);
|
||||
|
||||
const mockedTmpDir = join(tmpDir.path, ".tmp", "tmp");
|
||||
mockedTmpDirUri = Uri.file(mockedTmpDir);
|
||||
|
||||
workspaceFoldersSpy = jest
|
||||
.spyOn(workspace, "workspaceFolders", "get")
|
||||
.mockReturnValue([]);
|
||||
workspaceFileSpy = jest
|
||||
.spyOn(workspace, "workspaceFile", "get")
|
||||
.mockReturnValue(undefined);
|
||||
updateWorkspaceFoldersSpy = jest
|
||||
.spyOn(workspace, "updateWorkspaceFolders")
|
||||
.mockReturnValue(true);
|
||||
|
||||
jest.spyOn(files, "tmpdir").mockReturnValue(mockedTmpDir);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await tmpDir.cleanup();
|
||||
});
|
||||
|
||||
it("when a workspace folder with the correct path exists", async () => {
|
||||
workspaceFoldersSpy.mockReturnValue([
|
||||
{
|
||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-java"),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-python"),
|
||||
name: "codeql-custom-queries-python",
|
||||
index: 1,
|
||||
},
|
||||
{
|
||||
uri: extensionsDirectory,
|
||||
name: "CodeQL Extension Packs",
|
||||
index: 2,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(await autoPickExtensionsDirectory()).toEqual(extensionsDirectory);
|
||||
expect(updateWorkspaceFoldersSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("when a workspace file exists", async () => {
|
||||
workspaceFoldersSpy.mockReturnValue([
|
||||
{
|
||||
uri: Uri.file("/a/b/c"),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-python"),
|
||||
name: "codeql-custom-queries-python",
|
||||
index: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
workspaceFileSpy.mockReturnValue(
|
||||
Uri.joinPath(rootDirectory, "workspace.code-workspace"),
|
||||
);
|
||||
|
||||
expect(await autoPickExtensionsDirectory()).toEqual(extensionsDirectory);
|
||||
expect(updateWorkspaceFoldersSpy).toHaveBeenCalledWith(2, 0, {
|
||||
name: "CodeQL Extension Packs",
|
||||
uri: extensionsDirectory,
|
||||
});
|
||||
});
|
||||
|
||||
it("when updating the workspace folders fails", async () => {
|
||||
updateWorkspaceFoldersSpy.mockReturnValue(false);
|
||||
|
||||
workspaceFoldersSpy.mockReturnValue([
|
||||
{
|
||||
uri: Uri.file("/a/b/c"),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-python"),
|
||||
name: "codeql-custom-queries-python",
|
||||
index: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
workspaceFileSpy.mockReturnValue(
|
||||
Uri.joinPath(rootDirectory, "workspace.code-workspace"),
|
||||
);
|
||||
|
||||
expect(await autoPickExtensionsDirectory()).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("when a workspace file does not exist and there is a common root directory", async () => {
|
||||
workspaceFoldersSpy.mockReturnValue([
|
||||
{
|
||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-java"),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-python"),
|
||||
name: "codeql-custom-queries-python",
|
||||
index: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(await autoPickExtensionsDirectory()).toEqual(extensionsDirectory);
|
||||
expect(updateWorkspaceFoldersSpy).toHaveBeenCalledWith(2, 0, {
|
||||
name: "CodeQL Extension Packs",
|
||||
uri: extensionsDirectory,
|
||||
});
|
||||
});
|
||||
|
||||
it("when a workspace file does not exist and there is a temp dir as workspace folder", async () => {
|
||||
workspaceFoldersSpy.mockReturnValue([
|
||||
{
|
||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-java"),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-python"),
|
||||
name: "codeql-custom-queries-python",
|
||||
index: 1,
|
||||
},
|
||||
{
|
||||
uri: Uri.joinPath(mockedTmpDirUri, "quick-queries"),
|
||||
name: "quick-queries",
|
||||
index: 2,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(await autoPickExtensionsDirectory()).toEqual(extensionsDirectory);
|
||||
expect(updateWorkspaceFoldersSpy).toHaveBeenCalledWith(3, 0, {
|
||||
name: "CodeQL Extension Packs",
|
||||
uri: extensionsDirectory,
|
||||
});
|
||||
});
|
||||
|
||||
it("when a workspace file does not exist and there is no common root directory", async () => {
|
||||
workspaceFoldersSpy.mockReturnValue([
|
||||
{
|
||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-java"),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
uri: Uri.file("/a/b/c"),
|
||||
name: "codeql-custom-queries-python",
|
||||
index: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(await autoPickExtensionsDirectory()).toEqual(undefined);
|
||||
expect(updateWorkspaceFoldersSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("when a workspace file does not exist and there is a .git folder", async () => {
|
||||
await mkdirp(join(rootDirectory.fsPath, ".git"));
|
||||
|
||||
workspaceFoldersSpy.mockReturnValue([
|
||||
{
|
||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-java"),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
uri: Uri.file("/a/b/c"),
|
||||
name: "codeql-custom-queries-python",
|
||||
index: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(await autoPickExtensionsDirectory()).toEqual(extensionsDirectory);
|
||||
expect(updateWorkspaceFoldersSpy).toHaveBeenCalledWith(2, 0, {
|
||||
name: "CodeQL Extension Packs",
|
||||
uri: extensionsDirectory,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,7 +6,7 @@ import { createMockLogger } from "../../../__mocks__/loggerMock";
|
||||
import type { Uri } from "vscode";
|
||||
import { DatabaseKind } from "../../../../src/databases/local-databases";
|
||||
import { file } from "tmp-promise";
|
||||
import { QueryResultType } from "../../../../src/pure/new-messages";
|
||||
import { QueryResultType } from "../../../../src/query-server/new-messages";
|
||||
import { readdir, readFile } from "fs-extra";
|
||||
import { load } from "js-yaml";
|
||||
import { dirname, join } from "path";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EvalLogTreeBuilder } from "../../../../src/query-evaluation-logging";
|
||||
import { EvalLogData } from "../../../../src/pure/log-summary-parser";
|
||||
import { EvalLogData } from "../../../../src/log-insights/log-summary-parser";
|
||||
|
||||
describe("EvalLogTreeBuilder", () => {
|
||||
it("should build the log tree roots", async () => {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { QueryWithResults } from "../../../../../src/run-queries-shared";
|
||||
import { DatabaseInfo } from "../../../../../src/common/interface-types";
|
||||
import { CancellationTokenSource, Uri } from "vscode";
|
||||
import { tmpDir } from "../../../../../src/tmp-dir";
|
||||
import { QueryResultType } from "../../../../../src/pure/legacy-messages";
|
||||
import { QueryResultType } from "../../../../../src/query-server/legacy-messages";
|
||||
import { QueryInProgress } from "../../../../../src/query-server/legacy";
|
||||
import { VariantAnalysisHistoryItem } from "../../../../../src/query-history/variant-analysis-history-item";
|
||||
import { QueryHistoryInfo } from "../../../../../src/query-history/query-history-info";
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
import {
|
||||
EvaluationResult,
|
||||
QueryResultType,
|
||||
} from "../../../src/pure/legacy-messages";
|
||||
} from "../../../src/query-server/legacy-messages";
|
||||
import { sleep } from "../../../src/common/time";
|
||||
import { mockedObject } from "../utils/mocking.helpers";
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
compileQuery,
|
||||
registerDatabases,
|
||||
deregisterDatabases,
|
||||
} from "../../../src/pure/legacy-messages";
|
||||
} from "../../../src/query-server/legacy-messages";
|
||||
import * as config from "../../../src/config";
|
||||
import { tmpDir } from "../../../src/tmp-dir";
|
||||
import { CodeQLCliServer } from "../../../src/codeql-cli/cli";
|
||||
|
||||
Reference in New Issue
Block a user