Merge remote-tracking branch 'origin/main' into dbartol/save-before-start

This commit is contained in:
Dave Bartolomeo
2023-06-22 17:32:05 +00:00
83 changed files with 1116 additions and 186 deletions

View File

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

View File

@@ -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";

View File

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

View File

@@ -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();
}

View File

@@ -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";

View File

@@ -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>();
}

View File

@@ -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;
}

View File

@@ -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"],
};

View File

@@ -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;
}

View File

@@ -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";

View File

@@ -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";

View File

@@ -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) {

View File

@@ -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() {

View File

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

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

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

View File

@@ -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";

View File

@@ -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";

View File

@@ -7,7 +7,7 @@ import {
Dataset,
deregisterDatabases,
registerDatabases,
} from "../../pure/legacy-messages";
} from "../legacy-messages";
import {
CoreQueryResults,
CoreQueryTarget,

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

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

View File

@@ -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";

View File

@@ -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";

View File

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

View File

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

View File

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

View File

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

View File

@@ -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";

View File

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

View File

@@ -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";

View File

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

View File

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

View File

@@ -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%;

View File

@@ -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";

View File

@@ -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)`

View File

@@ -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 = {

View File

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

View File

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

View File

@@ -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";

View File

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

View File

@@ -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, () => {

View File

@@ -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, () => {

View File

@@ -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 = {

View File

@@ -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, () => {

View File

@@ -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({

View File

@@ -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}`);
}
});

View File

@@ -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");
}

View File

@@ -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));
},
);
});

View File

@@ -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", () => {

View File

@@ -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 = {

View File

@@ -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";

View File

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

View File

@@ -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";

View File

@@ -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";

View File

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

View File

@@ -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();
});
});

View File

@@ -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,
});
});
});

View File

@@ -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";

View File

@@ -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 () => {

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";