Merge branch 'main' into robertbrignull/contait push origin robertbrignull/contains_pathns_path

This commit is contained in:
Robert
2023-06-12 15:39:47 +01:00
26 changed files with 464 additions and 479 deletions

View File

@@ -22,11 +22,11 @@ import {
getErrorMessage,
getErrorStack,
} from "../pure/helpers-pure";
import { walkDirectory } from "../pure/files";
import { QueryMetadata, SortDirection } from "../pure/interface-types";
import { BaseLogger, Logger, ProgressReporter } from "../common";
import { CompilationMessage } from "../pure/legacy-messages";
import { sarifParser } from "../common/sarif-parser";
import { walkDirectory } from "../helpers";
import { App } from "../common/app";
import { QueryLanguage } from "../common/query-language";

View File

@@ -4,11 +4,6 @@ import { AppEventEmitter } from "./events";
import { Logger } from "./logging";
import { Memento } from "./memento";
import { AppCommandManager } from "./commands";
import type {
WorkspaceFolder,
Event,
WorkspaceFoldersChangeEvent,
} from "vscode";
export interface App {
createEventEmitter<T>(): AppEventEmitter<T>;
@@ -19,8 +14,6 @@ export interface App {
readonly globalStoragePath: string;
readonly workspaceStoragePath?: string;
readonly workspaceState: Memento;
readonly workspaceFolders: readonly WorkspaceFolder[] | undefined;
readonly onDidChangeWorkspaceFolders: Event<WorkspaceFoldersChangeEvent>;
readonly credentials: Credentials;
readonly commands: AppCommandManager;
readonly environment: EnvironmentContext;

View File

@@ -35,3 +35,7 @@ export const dbSchemeToLanguage = {
"ruby.dbscheme": "ruby",
"swift.dbscheme": "swift",
};
export function isQueryLanguage(language: string): language is QueryLanguage {
return Object.values(QueryLanguage).includes(language as QueryLanguage);
}

View File

@@ -40,14 +40,6 @@ export class ExtensionApp implements App {
return this.extensionContext.workspaceState;
}
public get workspaceFolders(): readonly vscode.WorkspaceFolder[] | undefined {
return vscode.workspace.workspaceFolders;
}
public get onDidChangeWorkspaceFolders(): vscode.Event<vscode.WorkspaceFoldersChangeEvent> {
return vscode.workspace.onDidChangeWorkspaceFolders;
}
public get subscriptions(): Disposable[] {
return this.extensionContext.subscriptions;
}

View File

@@ -4,10 +4,10 @@ import { writeFile } from "fs-extra";
import { dump as dumpYaml } from "js-yaml";
import {
getOnDiskWorkspaceFolders,
isQueryLanguage,
showAndLogExceptionWithTelemetry,
} from "../helpers";
import { TeeLogger } from "../common";
import { isQueryLanguage } from "../common/query-language";
import { CancellationToken } from "vscode";
import { CodeQLCliServer } from "../codeql-cli/cli";
import { DatabaseItem } from "../databases/local-databases";

View File

@@ -29,11 +29,13 @@ import {
withProgress,
} from "../common/vscode/progress";
import {
isLikelyDatabaseRoot,
isLikelyDbLanguageFolder,
showAndLogErrorMessage,
showAndLogExceptionWithTelemetry,
} from "../helpers";
import {
isLikelyDatabaseRoot,
isLikelyDbLanguageFolder,
} from "./local-databases/db-contents-heuristics";
import { extLogger } from "../common";
import {
importArchiveDatabase,

View File

@@ -10,7 +10,7 @@ import {
zipArchiveScheme,
} from "../../common/vscode/archive-filesystem-provider";
import { DatabaseItem, PersistedDatabaseItem } from "./database-item";
import { isLikelyDatabaseRoot } from "../../helpers";
import { isLikelyDatabaseRoot } from "./db-contents-heuristics";
import { stat } from "fs-extra";
import { pathsEqual } from "../../pure/files";
import { DatabaseContents } from "./database-contents";

View File

@@ -16,10 +16,10 @@ import { DatabaseItemImpl } from "./database-item-impl";
import {
getFirstWorkspaceFolder,
isFolderAlreadyInWorkspace,
isQueryLanguage,
showAndLogExceptionWithTelemetry,
showNeverAskAgainDialog,
} from "../../helpers";
import { isQueryLanguage } from "../../common/query-language";
import { existsSync } from "fs";
import { QlPackGenerator } from "../../qlpack-generator";
import { asError, getErrorMessage } from "../../pure/helpers-pure";

View File

@@ -0,0 +1,35 @@
import { pathExists } from "fs-extra";
import { basename, join } from "path";
import { glob } from "glob";
/**
* The following functions al heuristically determine metadata about databases.
*/
/**
* Heuristically determines if the directory passed in corresponds
* to a database root. A database root is a directory that contains
* a codeql-database.yml or (historically) a .dbinfo file. It also
* contains a folder starting with `db-`.
*/
export async function isLikelyDatabaseRoot(maybeRoot: string) {
const [a, b, c] = await Promise.all([
// databases can have either .dbinfo or codeql-database.yml.
pathExists(join(maybeRoot, ".dbinfo")),
pathExists(join(maybeRoot, "codeql-database.yml")),
// they *must* have a db-{language} folder
glob("db-*/", { cwd: maybeRoot }),
]);
return (a || b) && c.length > 0;
}
/**
* A language folder is any folder starting with `db-` that is itself not a database root.
*/
export async function isLikelyDbLanguageFolder(dbPath: string) {
return (
basename(dbPath).startsWith("db-") && !(await isLikelyDatabaseRoot(dbPath))
);
}

View File

@@ -0,0 +1,130 @@
import { window } from "vscode";
import { glob } from "glob";
import { basename } from "path";
import { load } from "js-yaml";
import { readFile } from "fs-extra";
import { getQlPackPath } from "../pure/ql";
import { CodeQLCliServer, QlpacksInfo } from "../codeql-cli/cli";
import { extLogger } from "../common";
import { getOnDiskWorkspaceFolders } from "../helpers";
export interface QlPacksForLanguage {
/** The name of the pack containing the dbscheme. */
dbschemePack: string;
/** `true` if `dbschemePack` is a library pack. */
dbschemePackIsLibraryPack: boolean;
/**
* The name of the corresponding standard query pack.
* Only defined if `dbschemePack` is a library pack.
*/
queryPack?: string;
}
interface QlPackWithPath {
packName: string;
packDir: string | undefined;
}
async function findDbschemePack(
packs: QlPackWithPath[],
dbschemePath: string,
): Promise<{ name: string; isLibraryPack: boolean }> {
for (const { packDir, packName } of packs) {
if (packDir !== undefined) {
const qlpackPath = await getQlPackPath(packDir);
if (qlpackPath !== undefined) {
const qlpack = load(await readFile(qlpackPath, "utf8")) as {
dbscheme?: string;
library?: boolean;
};
if (
qlpack.dbscheme !== undefined &&
basename(qlpack.dbscheme) === basename(dbschemePath)
) {
return {
name: packName,
isLibraryPack: qlpack.library === true,
};
}
}
}
}
throw new Error(`Could not find qlpack file for dbscheme ${dbschemePath}`);
}
function findStandardQueryPack(
qlpacks: QlpacksInfo,
dbschemePackName: string,
): string | undefined {
const matches = dbschemePackName.match(/^codeql\/(?<language>[a-z]+)-all$/);
if (matches) {
const queryPackName = `codeql/${matches.groups!.language}-queries`;
if (qlpacks[queryPackName] !== undefined) {
return queryPackName;
}
}
// Either the dbscheme pack didn't look like one where the queries might be in the query pack, or
// no query pack was found in the search path. Either is OK.
return undefined;
}
export async function getQlPackForDbscheme(
cliServer: Pick<CodeQLCliServer, "resolveQlpacks">,
dbschemePath: string,
): Promise<QlPacksForLanguage> {
const qlpacks = await cliServer.resolveQlpacks(getOnDiskWorkspaceFolders());
const packs: QlPackWithPath[] = Object.entries(qlpacks).map(
([packName, dirs]) => {
if (dirs.length < 1) {
void extLogger.log(
`In getQlPackFor ${dbschemePath}, qlpack ${packName} has no directories`,
);
return { packName, packDir: undefined };
}
if (dirs.length > 1) {
void extLogger.log(
`In getQlPackFor ${dbschemePath}, qlpack ${packName} has more than one directory; arbitrarily choosing the first`,
);
}
return {
packName,
packDir: dirs[0],
};
},
);
const dbschemePack = await findDbschemePack(packs, dbschemePath);
const queryPack = dbschemePack.isLibraryPack
? findStandardQueryPack(qlpacks, dbschemePack.name)
: undefined;
return {
dbschemePack: dbschemePack.name,
dbschemePackIsLibraryPack: dbschemePack.isLibraryPack,
queryPack,
};
}
export async function getPrimaryDbscheme(
datasetFolder: string,
): Promise<string> {
const dbschemes = await glob("*.dbscheme", {
cwd: datasetFolder,
});
if (dbschemes.length < 1) {
throw new Error(
`Can't find dbscheme for current database in ${datasetFolder}`,
);
}
dbschemes.sort();
const dbscheme = dbschemes[0];
if (dbschemes.length > 1) {
void window.showErrorMessage(
`Found multiple dbschemes in ${datasetFolder} during quick query; arbitrarily choosing the first, ${dbscheme}, to decide what library to use.`,
);
}
return dbscheme;
}

View File

@@ -1,24 +1,14 @@
import {
ensureDirSync,
readFile,
pathExists,
ensureDir,
writeFile,
opendir,
} from "fs-extra";
import { glob } from "glob";
import { load } from "js-yaml";
import { join, basename, dirname } from "path";
import { ensureDirSync, pathExists, ensureDir, writeFile } from "fs-extra";
import { join, dirname } from "path";
import { dirSync } from "tmp-promise";
import { Uri, window as Window, workspace, env, WorkspaceFolder } from "vscode";
import { CodeQLCliServer, QlpacksInfo } from "./codeql-cli/cli";
import { CodeQLCliServer } from "./codeql-cli/cli";
import { UserCancellationException } from "./common/vscode/progress";
import { extLogger, OutputChannelLogger } from "./common";
import { QueryMetadata } from "./pure/interface-types";
import { telemetryListener } from "./telemetry";
import { RedactableError } from "./pure/errors";
import { getQlPackPath } from "./pure/ql";
import { dbSchemeToLanguage, QueryLanguage } from "./common/query-language";
import { isQueryLanguage, QueryLanguage } from "./common/query-language";
import { isCodespacesTemplate } from "./config";
import { AppCommandManager } from "./common/commands";
@@ -356,202 +346,6 @@ export async function prepareCodeTour(
}
}
export interface QlPacksForLanguage {
/** The name of the pack containing the dbscheme. */
dbschemePack: string;
/** `true` if `dbschemePack` is a library pack. */
dbschemePackIsLibraryPack: boolean;
/**
* The name of the corresponding standard query pack.
* Only defined if `dbschemePack` is a library pack.
*/
queryPack?: string;
}
interface QlPackWithPath {
packName: string;
packDir: string | undefined;
}
async function findDbschemePack(
packs: QlPackWithPath[],
dbschemePath: string,
): Promise<{ name: string; isLibraryPack: boolean }> {
for (const { packDir, packName } of packs) {
if (packDir !== undefined) {
const qlpackPath = await getQlPackPath(packDir);
if (qlpackPath !== undefined) {
const qlpack = load(await readFile(qlpackPath, "utf8")) as {
dbscheme?: string;
library?: boolean;
};
if (
qlpack.dbscheme !== undefined &&
basename(qlpack.dbscheme) === basename(dbschemePath)
) {
return {
name: packName,
isLibraryPack: qlpack.library === true,
};
}
}
}
}
throw new Error(`Could not find qlpack file for dbscheme ${dbschemePath}`);
}
function findStandardQueryPack(
qlpacks: QlpacksInfo,
dbschemePackName: string,
): string | undefined {
const matches = dbschemePackName.match(/^codeql\/(?<language>[a-z]+)-all$/);
if (matches) {
const queryPackName = `codeql/${matches.groups!.language}-queries`;
if (qlpacks[queryPackName] !== undefined) {
return queryPackName;
}
}
// Either the dbscheme pack didn't look like one where the queries might be in the query pack, or
// no query pack was found in the search path. Either is OK.
return undefined;
}
export async function getQlPackForDbscheme(
cliServer: Pick<CodeQLCliServer, "resolveQlpacks">,
dbschemePath: string,
): Promise<QlPacksForLanguage> {
const qlpacks = await cliServer.resolveQlpacks(getOnDiskWorkspaceFolders());
const packs: QlPackWithPath[] = Object.entries(qlpacks).map(
([packName, dirs]) => {
if (dirs.length < 1) {
void extLogger.log(
`In getQlPackFor ${dbschemePath}, qlpack ${packName} has no directories`,
);
return { packName, packDir: undefined };
}
if (dirs.length > 1) {
void extLogger.log(
`In getQlPackFor ${dbschemePath}, qlpack ${packName} has more than one directory; arbitrarily choosing the first`,
);
}
return {
packName,
packDir: dirs[0],
};
},
);
const dbschemePack = await findDbschemePack(packs, dbschemePath);
const queryPack = dbschemePack.isLibraryPack
? findStandardQueryPack(qlpacks, dbschemePack.name)
: undefined;
return {
dbschemePack: dbschemePack.name,
dbschemePackIsLibraryPack: dbschemePack.isLibraryPack,
queryPack,
};
}
export async function getPrimaryDbscheme(
datasetFolder: string,
): Promise<string> {
const dbschemes = await glob("*.dbscheme", {
cwd: datasetFolder,
});
if (dbschemes.length < 1) {
throw new Error(
`Can't find dbscheme for current database in ${datasetFolder}`,
);
}
dbschemes.sort();
const dbscheme = dbschemes[0];
if (dbschemes.length > 1) {
void Window.showErrorMessage(
`Found multiple dbschemes in ${datasetFolder} during quick query; arbitrarily choosing the first, ${dbscheme}, to decide what library to use.`,
);
}
return dbscheme;
}
/**
* The following functions al heuristically determine metadata about databases.
*/
/**
* Note that this heuristic is only being used for backwards compatibility with
* CLI versions before the langauge name was introduced to dbInfo. Features
* that do not require backwards compatibility should call
* `cli.CodeQLCliServer.resolveDatabase` and use the first entry in the
* `languages` property.
*
* @see cli.CodeQLCliServer.resolveDatabase
*/
export const languageToDbScheme = Object.entries(dbSchemeToLanguage).reduce(
(acc, [k, v]) => {
acc[v] = k;
return acc;
},
{} as { [k: string]: string },
);
/**
* Returns the initial contents for an empty query, based on the language of the selected
* databse.
*
* First try to use the given language name. If that doesn't exist, try to infer it based on
* dbscheme. Otherwise return no import statement.
*
* @param language the database language or empty string if unknown
* @param dbscheme path to the dbscheme file
*
* @returns an import and empty select statement appropriate for the selected language
*/
export function getInitialQueryContents(language: string, dbscheme: string) {
if (!language) {
const dbschemeBase = basename(dbscheme) as keyof typeof dbSchemeToLanguage;
language = dbSchemeToLanguage[dbschemeBase];
}
return language ? `import ${language}\n\nselect ""` : 'select ""';
}
/**
* Heuristically determines if the directory passed in corresponds
* to a database root. A database root is a directory that contains
* a codeql-database.yml or (historically) a .dbinfo file. It also
* contains a folder starting with `db-`.
*/
export async function isLikelyDatabaseRoot(maybeRoot: string) {
const [a, b, c] = await Promise.all([
// databases can have either .dbinfo or codeql-database.yml.
pathExists(join(maybeRoot, ".dbinfo")),
pathExists(join(maybeRoot, "codeql-database.yml")),
// they *must* have a db-{language} folder
glob("db-*/", { cwd: maybeRoot }),
]);
return (a || b) && c.length > 0;
}
/**
* A language folder is any folder starting with `db-` that is itself not a database root.
*/
export async function isLikelyDbLanguageFolder(dbPath: string) {
return (
basename(dbPath).startsWith("db-") && !(await isLikelyDatabaseRoot(dbPath))
);
}
export function isQueryLanguage(language: string): language is QueryLanguage {
return Object.values(QueryLanguage).includes(language as QueryLanguage);
}
/**
* Finds the language that a query targets.
* If it can't be autodetected, prompt the user to specify the language manually.
@@ -655,29 +449,6 @@ export async function createTimestampFile(storagePath: string) {
await writeFile(timestampPath, Date.now().toString(), "utf8");
}
/**
* Recursively walk a directory and return the full path to all files found.
* Symbolic links are ignored.
*
* @param dir the directory to walk
*
* @return An iterator of the full path to all files recursively found in the directory.
*/
export async function* walkDirectory(
dir: string,
): AsyncIterableIterator<string> {
const seenFiles = new Set<string>();
for await (const d of await opendir(dir)) {
const entry = join(dir, d.name);
seenFiles.add(entry);
if (d.isDirectory()) {
yield* walkDirectory(entry);
} else if (d.isFile()) {
yield entry;
}
}
}
/**
* Returns the path of the first folder in the workspace.
* This is used to decide where to create skeleton QL packs.

View File

@@ -4,12 +4,14 @@ import { file } from "tmp-promise";
import { basename, dirname, resolve } from "path";
import {
getPrimaryDbscheme,
getQlPackForDbscheme,
getOnDiskWorkspaceFolders,
QlPacksForLanguage,
showAndLogExceptionWithTelemetry,
} from "../../helpers";
import {
getPrimaryDbscheme,
getQlPackForDbscheme,
QlPacksForLanguage,
} from "../../databases/qlpack";
import {
KeyType,
kindOfKeyType,

View File

@@ -0,0 +1,23 @@
import { basename } from "path";
import { dbSchemeToLanguage } from "../common/query-language";
/**
* Returns the initial contents for an empty query, based on the language of the selected
* databse.
*
* First try to use the given language name. If that doesn't exist, try to infer it based on
* dbscheme. Otherwise return no import statement.
*
* @param language the database language or empty string if unknown
* @param dbscheme path to the dbscheme file
*
* @returns an import and empty select statement appropriate for the selected language
*/
export function getInitialQueryContents(language: string, dbscheme: string) {
if (!language) {
const dbschemeBase = basename(dbscheme) as keyof typeof dbSchemeToLanguage;
language = dbSchemeToLanguage[dbschemeBase];
}
return language ? `import ${language}\n\nselect ""` : 'select ""';
}

View File

@@ -5,12 +5,9 @@ import { CancellationToken, window as Window, workspace, Uri } from "vscode";
import { LSPErrorCodes, ResponseError } from "vscode-languageclient";
import { CodeQLCliServer } from "../codeql-cli/cli";
import { DatabaseUI } from "../databases/local-databases-ui";
import {
getInitialQueryContents,
getPrimaryDbscheme,
getQlPackForDbscheme,
showBinaryChoiceDialog,
} from "../helpers";
import { showBinaryChoiceDialog } from "../helpers";
import { getInitialQueryContents } from "./query-contents";
import { getPrimaryDbscheme, getQlPackForDbscheme } from "../databases/qlpack";
import {
ProgressCallback,
UserCancellationException,

View File

@@ -1,4 +1,4 @@
import { pathExists, stat, readdir } from "fs-extra";
import { pathExists, stat, readdir, opendir } from "fs-extra";
import { isAbsolute, join, relative, resolve } from "path";
/**
@@ -84,3 +84,26 @@ export async function readDirFullPaths(path: string): Promise<string[]> {
const baseNames = await readdir(path);
return baseNames.map((baseName) => join(path, baseName));
}
/**
* Recursively walk a directory and return the full path to all files found.
* Symbolic links are ignored.
*
* @param dir the directory to walk
*
* @return An iterator of the full path to all files recursively found in the directory.
*/
export async function* walkDirectory(
dir: string,
): AsyncIterableIterator<string> {
const seenFiles = new Set<string>();
for await (const d of await opendir(dir)) {
const entry = join(dir, d.name);
seenFiles.add(entry);
if (d.isDirectory()) {
yield* walkDirectory(entry);
} else if (d.isFile()) {
yield entry;
}
}
}

View File

@@ -19,7 +19,7 @@ export class QueriesModule extends DisposableObject {
}
void extLogger.log("Initializing queries panel.");
const queryDiscovery = new QueryDiscovery(app, cliServer);
const queryDiscovery = new QueryDiscovery(app.environment, cliServer);
this.push(queryDiscovery);
void queryDiscovery.refresh();

View File

@@ -1,9 +1,16 @@
import { dirname, basename, normalize, relative } from "path";
import { Discovery } from "../common/discovery";
import { CodeQLCliServer } from "../codeql-cli/cli";
import { Event, RelativePattern, Uri, WorkspaceFolder } from "vscode";
import {
Event,
EventEmitter,
RelativePattern,
Uri,
WorkspaceFolder,
workspace,
} from "vscode";
import { MultiFileSystemWatcher } from "../common/vscode/multi-file-system-watcher";
import { App } from "../common/app";
import { EnvironmentContext } from "../common/app";
import { FileTreeDirectory, FileTreeLeaf } from "../common/file-tree-nodes";
import { getOnDiskWorkspaceFoldersObjects } from "../helpers";
import { AppEventEmitter } from "../common/events";
@@ -42,13 +49,13 @@ export class QueryDiscovery
);
constructor(
private readonly app: App,
private readonly env: EnvironmentContext,
private readonly cliServer: CodeQLCliServer,
) {
super("Query Discovery", extLogger);
this.onDidChangeQueriesEmitter = this.push(app.createEventEmitter<void>());
this.push(app.onDidChangeWorkspaceFolders(this.refresh.bind(this)));
this.onDidChangeQueriesEmitter = this.push(new EventEmitter<void>());
this.push(workspace.onDidChangeWorkspaceFolders(this.refresh.bind(this)));
this.push(this.watcher.onDidChange(this.refresh.bind(this)));
}
@@ -130,7 +137,7 @@ export class QueryDiscovery
const rootDirectory = new FileTreeDirectory<string>(
fullPath,
name,
this.app.environment,
this.env,
);
for (const queryPath of resolvedQueries) {
const relativePath = normalize(relative(fullPath, queryPath));

View File

@@ -8,11 +8,6 @@ import { testCredentialsWithStub } from "../factories/authentication";
import { Credentials } from "../../src/common/authentication";
import { AppCommandManager } from "../../src/common/commands";
import { createMockCommandManager } from "./commandsMock";
import type {
Event,
WorkspaceFolder,
WorkspaceFoldersChangeEvent,
} from "vscode";
export function createMockApp({
extensionPath = "/mock/extension/path",
@@ -20,8 +15,6 @@ export function createMockApp({
globalStoragePath = "/mock/global/storage/path",
createEventEmitter = <T>() => new MockAppEventEmitter<T>(),
workspaceState = createMockMemento(),
workspaceFolders = [],
onDidChangeWorkspaceFolders = jest.fn(),
credentials = testCredentialsWithStub(),
commands = createMockCommandManager(),
environment = createMockEnvironmentContext(),
@@ -31,8 +24,6 @@ export function createMockApp({
globalStoragePath?: string;
createEventEmitter?: <T>() => AppEventEmitter<T>;
workspaceState?: Memento;
workspaceFolders?: readonly WorkspaceFolder[] | undefined;
onDidChangeWorkspaceFolders?: Event<WorkspaceFoldersChangeEvent>;
credentials?: Credentials;
commands?: AppCommandManager;
environment?: EnvironmentContext;
@@ -45,8 +36,6 @@ export function createMockApp({
workspaceStoragePath,
globalStoragePath,
workspaceState,
workspaceFolders,
onDidChangeWorkspaceFolders,
createEventEmitter,
credentials,
commands,

View File

@@ -0,0 +1,76 @@
import * as tmp from "tmp";
import { join } from "path";
import { mkdirSync, writeFileSync } from "fs-extra";
import {
isLikelyDatabaseRoot,
isLikelyDbLanguageFolder,
} from "../../../../src/databases/local-databases/db-contents-heuristics";
describe("isLikelyDatabaseRoot", () => {
let dir: tmp.DirResult;
beforeEach(() => {
dir = tmp.dirSync();
});
afterEach(() => {
dir.removeCallback();
});
it("should likely be a database root: codeql-database.yml", async () => {
const dbFolder = join(dir.name, "db");
mkdirSync(dbFolder);
mkdirSync(join(dbFolder, "db-python"));
writeFileSync(join(dbFolder, "codeql-database.yml"), "", "utf8");
expect(await isLikelyDatabaseRoot(dbFolder)).toBe(true);
});
it("should likely be a database root: .dbinfo", async () => {
const dbFolder = join(dir.name, "db");
mkdirSync(dbFolder);
mkdirSync(join(dbFolder, "db-python"));
writeFileSync(join(dbFolder, ".dbinfo"), "", "utf8");
expect(await isLikelyDatabaseRoot(dbFolder)).toBe(true);
});
it("should likely NOT be a database root: empty dir", async () => {
const dbFolder = join(dir.name, "db");
mkdirSync(dbFolder);
mkdirSync(join(dbFolder, "db-python"));
expect(await isLikelyDatabaseRoot(dbFolder)).toBe(false);
});
it("should likely NOT be a database root: no db language folder", async () => {
const dbFolder = join(dir.name, "db");
mkdirSync(dbFolder);
writeFileSync(join(dbFolder, ".dbinfo"), "", "utf8");
expect(await isLikelyDatabaseRoot(dbFolder)).toBe(false);
});
});
describe("isLikelyDbLanguageFolder", () => {
let dir: tmp.DirResult;
beforeEach(() => {
dir = tmp.dirSync();
});
afterEach(() => {
dir.removeCallback();
});
it("should find likely db language folder", async () => {
const dbFolder = join(dir.name, "db-python");
mkdirSync(dbFolder);
mkdirSync(join(dbFolder, "db-python"));
writeFileSync(join(dbFolder, "codeql-database.yml"), "", "utf8");
// not a db folder since there is a db-python folder inside this one
expect(await isLikelyDbLanguageFolder(dbFolder)).toBe(false);
const nestedDbPythonFolder = join(dbFolder, "db-python");
expect(await isLikelyDbLanguageFolder(nestedDbPythonFolder)).toBe(true);
});
});

View File

@@ -0,0 +1,41 @@
import * as tmp from "tmp";
import { dump } from "js-yaml";
import { writeFileSync } from "fs-extra";
import { join } from "path";
import { QueryLanguage } from "../../../src/common/query-language";
import { getInitialQueryContents } from "../../../src/local-queries/query-contents";
describe("getInitialQueryContents", () => {
let dir: tmp.DirResult;
let language: QueryLanguage;
beforeEach(() => {
dir = tmp.dirSync();
language = QueryLanguage.Cpp;
const contents = dump({
primaryLanguage: language,
});
writeFileSync(join(dir.name, "codeql-database.yml"), contents, "utf8");
});
afterEach(() => {
dir.removeCallback();
});
it("should get initial query contents when language is known", () => {
expect(getInitialQueryContents(language, "hucairz")).toBe(
'import cpp\n\nselect ""',
);
});
it("should get initial query contents when dbscheme is known", () => {
expect(getInitialQueryContents("", "semmlecode.cpp.dbscheme")).toBe(
'import cpp\n\nselect ""',
);
});
it("should get initial query contents when nothing is known", () => {
expect(getInitialQueryContents("", "hucairz")).toBe('select ""');
});
});

View File

@@ -6,7 +6,11 @@ import {
getDirectoryNamesInsidePath,
pathsEqual,
readDirFullPaths,
walkDirectory,
} from "../../../src/pure/files";
import { DirResult } from "tmp";
import * as tmp from "tmp";
import { ensureDirSync, symlinkSync, writeFileSync } from "fs-extra";
describe("files", () => {
const dataDir = join(__dirname, "../../data");
@@ -339,3 +343,67 @@ describe("containsPath", () => {
},
);
});
describe("walkDirectory", () => {
let tmpDir: DirResult;
let dir: string;
let dir2: string;
beforeEach(() => {
tmpDir = tmp.dirSync({ unsafeCleanup: true });
dir = join(tmpDir.name, "dir");
ensureDirSync(dir);
dir2 = join(tmpDir.name, "dir2");
});
afterEach(() => {
tmpDir.removeCallback();
});
it("should walk a directory", async () => {
const file1 = join(dir, "file1");
const file2 = join(dir, "file2");
const file3 = join(dir, "file3");
const dir3 = join(dir, "dir3");
const file4 = join(dir, "file4");
const file5 = join(dir, "file5");
const file6 = join(dir, "file6");
// These symlinks link back to paths that are already existing, so ignore.
const symLinkFile7 = join(dir, "symlink0");
const symlinkDir = join(dir2, "symlink1");
// some symlinks that point outside of the base dir.
const file8 = join(tmpDir.name, "file8");
const file9 = join(dir2, "file8");
const symlinkDir2 = join(dir2, "symlink2");
const symlinkFile2 = join(dir2, "symlinkFile3");
ensureDirSync(dir2);
ensureDirSync(dir3);
writeFileSync(file1, "file1");
writeFileSync(file2, "file2");
writeFileSync(file3, "file3");
writeFileSync(file4, "file4");
writeFileSync(file5, "file5");
writeFileSync(file6, "file6");
writeFileSync(file8, "file8");
writeFileSync(file9, "file9");
// We don't really need to be testing all of these variants of symlinks,
// but it doesn't hurt, and will help us if we ever do decide to support them.
symlinkSync(file6, symLinkFile7, "file");
symlinkSync(dir3, symlinkDir, "dir");
symlinkSync(file8, symlinkFile2, "file");
symlinkSync(dir2, symlinkDir2, "dir");
const files = [];
for await (const file of walkDirectory(dir)) {
files.push(file);
}
// Only real files should be returned.
expect(files.sort()).toEqual([file1, file2, file3, file4, file5, file6]);
});
});

View File

@@ -7,15 +7,13 @@ import {
QueryInfoByLanguage,
} from "../../../src/codeql-cli/cli";
import { itWithCodeQL } from "../cli";
import {
getOnDiskWorkspaceFolders,
getQlPackForDbscheme,
languageToDbScheme,
} from "../../../src/helpers";
import { getOnDiskWorkspaceFolders } from "../../../src/helpers";
import { KeyType, resolveQueries } from "../../../src/language-support";
import { faker } from "@faker-js/faker";
import { getActivatedExtension } from "../global.helper";
import { BaseLogger } from "../../../src/common";
import { getQlPackForDbscheme } from "../../../src/databases/qlpack";
import { dbSchemeToLanguage } from "../../../src/common/query-language";
/**
* Perform proper integration tests by running the CLI
@@ -26,6 +24,14 @@ describe("Use cli", () => {
let logSpy: jest.SpiedFunction<BaseLogger["log"]>;
const languageToDbScheme = Object.entries(dbSchemeToLanguage).reduce(
(acc, [k, v]) => {
acc[v] = k;
return acc;
},
{} as { [k: string]: string },
);
beforeEach(async () => {
const extension = await getActivatedExtension();
cli = extension.cliServer;

View File

@@ -7,7 +7,7 @@ import {
} from "vscode";
import { CodeQLCliServer } from "../../../../src/codeql-cli/cli";
import { QueryDiscovery } from "../../../../src/queries-panel/query-discovery";
import { createMockApp } from "../../../__mocks__/appMock";
import { createMockEnvironmentContext } from "../../../__mocks__/appMock";
import { mockedObject } from "../../utils/mocking.helpers";
import { basename, join, sep } from "path";
@@ -23,7 +23,7 @@ describe("QueryDiscovery", () => {
resolveQueries,
});
const discovery = new QueryDiscovery(createMockApp({}), cli);
const discovery = new QueryDiscovery(createMockEnvironmentContext(), cli);
await discovery.refresh();
const queries = discovery.queries;
@@ -43,7 +43,7 @@ describe("QueryDiscovery", () => {
]),
});
const discovery = new QueryDiscovery(createMockApp({}), cli);
const discovery = new QueryDiscovery(createMockEnvironmentContext(), cli);
await discovery.refresh();
const queries = discovery.queries;
expect(queries).toBeDefined();
@@ -69,7 +69,7 @@ describe("QueryDiscovery", () => {
]),
});
const discovery = new QueryDiscovery(createMockApp({}), cli);
const discovery = new QueryDiscovery(createMockEnvironmentContext(), cli);
await discovery.refresh();
const queries = discovery.queries;
expect(queries).toBeDefined();
@@ -114,7 +114,7 @@ describe("QueryDiscovery", () => {
resolveQueries,
});
const discovery = new QueryDiscovery(createMockApp({}), cli);
const discovery = new QueryDiscovery(createMockEnvironmentContext(), cli);
await discovery.refresh();
const queries = discovery.queries;
expect(queries).toBeDefined();
@@ -153,12 +153,7 @@ describe("QueryDiscovery", () => {
.mockResolvedValue([join(workspaceRoot, "query1.ql")]),
});
const discovery = new QueryDiscovery(
createMockApp({
createEventEmitter: () => new EventEmitter(),
}),
cli,
);
const discovery = new QueryDiscovery(createMockEnvironmentContext(), cli);
const onDidChangeQueriesSpy = jest.fn();
discovery.onDidChangeQueries(onDidChangeQueriesSpy);
@@ -180,12 +175,12 @@ describe("QueryDiscovery", () => {
it("should refresh when workspace folders change", async () => {
const onDidChangeWorkspaceFoldersEvent =
new EventEmitter<WorkspaceFoldersChangeEvent>();
jest
.spyOn(workspace, "onDidChangeWorkspaceFolders")
.mockImplementation(onDidChangeWorkspaceFoldersEvent.event);
const discovery = new QueryDiscovery(
createMockApp({
createEventEmitter: () => new EventEmitter(),
onDidChangeWorkspaceFolders: onDidChangeWorkspaceFoldersEvent.event,
}),
createMockEnvironmentContext(),
mockedObject<CodeQLCliServer>({
resolveQueries: jest.fn().mockResolvedValue([]),
}),

View File

@@ -1,129 +1,22 @@
import { Uri, window, workspace, WorkspaceFolder } from "vscode";
import { dump } from "js-yaml";
import * as tmp from "tmp";
import { join } from "path";
import {
writeFileSync,
mkdirSync,
ensureDirSync,
symlinkSync,
writeFile,
mkdir,
} from "fs-extra";
import { DirResult } from "tmp";
import { writeFile, mkdir } from "fs-extra";
import {
getFirstWorkspaceFolder,
getInitialQueryContents,
isFolderAlreadyInWorkspace,
isLikelyDatabaseRoot,
isLikelyDbLanguageFolder,
prepareCodeTour,
showBinaryChoiceDialog,
showBinaryChoiceWithUrlDialog,
showInformationMessageWithAction,
showNeverAskAgainDialog,
walkDirectory,
} from "../../../src/helpers";
import { reportStreamProgress } from "../../../src/common/vscode/progress";
import { QueryLanguage } from "../../../src/common/query-language";
import { Setting } from "../../../src/config";
import { createMockCommandManager } from "../../__mocks__/commandsMock";
describe("helpers", () => {
describe("codeql-database.yml tests", () => {
let dir: tmp.DirResult;
let language: QueryLanguage;
beforeEach(() => {
dir = tmp.dirSync();
language = QueryLanguage.Cpp;
const contents = dump({
primaryLanguage: language,
});
writeFileSync(join(dir.name, "codeql-database.yml"), contents, "utf8");
});
afterEach(() => {
dir.removeCallback();
});
it("should get initial query contents when language is known", () => {
expect(getInitialQueryContents(language, "hucairz")).toBe(
'import cpp\n\nselect ""',
);
});
it("should get initial query contents when dbscheme is known", () => {
expect(getInitialQueryContents("", "semmlecode.cpp.dbscheme")).toBe(
'import cpp\n\nselect ""',
);
});
it("should get initial query contents when nothing is known", () => {
expect(getInitialQueryContents("", "hucairz")).toBe('select ""');
});
});
describe("likely database tests", () => {
let dir: tmp.DirResult;
beforeEach(() => {
dir = tmp.dirSync();
});
afterEach(() => {
dir.removeCallback();
});
it("should likely be a database root: codeql-database.yml", async () => {
const dbFolder = join(dir.name, "db");
mkdirSync(dbFolder);
mkdirSync(join(dbFolder, "db-python"));
writeFileSync(join(dbFolder, "codeql-database.yml"), "", "utf8");
expect(await isLikelyDatabaseRoot(dbFolder)).toBe(true);
});
it("should likely be a database root: .dbinfo", async () => {
const dbFolder = join(dir.name, "db");
mkdirSync(dbFolder);
mkdirSync(join(dbFolder, "db-python"));
writeFileSync(join(dbFolder, ".dbinfo"), "", "utf8");
expect(await isLikelyDatabaseRoot(dbFolder)).toBe(true);
});
it("should likely NOT be a database root: empty dir", async () => {
const dbFolder = join(dir.name, "db");
mkdirSync(dbFolder);
mkdirSync(join(dbFolder, "db-python"));
expect(await isLikelyDatabaseRoot(dbFolder)).toBe(false);
});
it("should likely NOT be a database root: no db language folder", async () => {
const dbFolder = join(dir.name, "db");
mkdirSync(dbFolder);
writeFileSync(join(dbFolder, ".dbinfo"), "", "utf8");
expect(await isLikelyDatabaseRoot(dbFolder)).toBe(false);
});
it("should find likely db language folder", async () => {
const dbFolder = join(dir.name, "db-python");
mkdirSync(dbFolder);
mkdirSync(join(dbFolder, "db-python"));
writeFileSync(join(dbFolder, "codeql-database.yml"), "", "utf8");
// not a db folder since there is a db-python folder inside this one
expect(await isLikelyDbLanguageFolder(dbFolder)).toBe(false);
const nestedDbPythonFolder = join(dbFolder, "db-python");
expect(await isLikelyDbLanguageFolder(nestedDbPythonFolder)).toBe(true);
});
});
it("should report stream progress", () => {
const progressSpy = jest.fn();
const mockReadable = {
@@ -342,70 +235,6 @@ describe("helpers", () => {
});
});
describe("walkDirectory", () => {
let tmpDir: DirResult;
let dir: string;
let dir2: string;
beforeEach(() => {
tmpDir = tmp.dirSync({ unsafeCleanup: true });
dir = join(tmpDir.name, "dir");
ensureDirSync(dir);
dir2 = join(tmpDir.name, "dir2");
});
afterEach(() => {
tmpDir.removeCallback();
});
it("should walk a directory", async () => {
const file1 = join(dir, "file1");
const file2 = join(dir, "file2");
const file3 = join(dir, "file3");
const dir3 = join(dir, "dir3");
const file4 = join(dir, "file4");
const file5 = join(dir, "file5");
const file6 = join(dir, "file6");
// These symlinks link back to paths that are already existing, so ignore.
const symLinkFile7 = join(dir, "symlink0");
const symlinkDir = join(dir2, "symlink1");
// some symlinks that point outside of the base dir.
const file8 = join(tmpDir.name, "file8");
const file9 = join(dir2, "file8");
const symlinkDir2 = join(dir2, "symlink2");
const symlinkFile2 = join(dir2, "symlinkFile3");
ensureDirSync(dir2);
ensureDirSync(dir3);
writeFileSync(file1, "file1");
writeFileSync(file2, "file2");
writeFileSync(file3, "file3");
writeFileSync(file4, "file4");
writeFileSync(file5, "file5");
writeFileSync(file6, "file6");
writeFileSync(file8, "file8");
writeFileSync(file9, "file9");
// We don't really need to be testing all of these variants of symlinks,
// but it doesn't hurt, and will help us if we ever do decide to support them.
symlinkSync(file6, symLinkFile7, "file");
symlinkSync(dir3, symlinkDir, "dir");
symlinkSync(file8, symlinkFile2, "file");
symlinkSync(dir2, symlinkDir2, "dir");
const files = [];
for await (const file of walkDirectory(dir)) {
files.push(file);
}
// Only real files should be returned.
expect(files.sort()).toEqual([file1, file2, file3, file4, file5, file6]);
});
});
describe("isFolderAlreadyInWorkspace", () => {
beforeEach(() => {
const folders = [

View File

@@ -4,6 +4,7 @@ import * as fs from "fs-extra";
import { getErrorMessage } from "../../../../../src/pure/helpers-pure";
import * as helpers from "../../../../../src/helpers";
import * as qlpack from "../../../../../src/databases/qlpack";
import {
KeyType,
qlpackOfDatabase,
@@ -14,10 +15,10 @@ import { mockDatabaseItem, mockedObject } from "../../../utils/mocking.helpers";
describe("queryResolver", () => {
let getQlPackForDbschemeSpy: jest.SpiedFunction<
typeof helpers.getQlPackForDbscheme
typeof qlpack.getQlPackForDbscheme
>;
let getPrimaryDbschemeSpy: jest.SpiedFunction<
typeof helpers.getPrimaryDbscheme
typeof qlpack.getPrimaryDbscheme
>;
const resolveQueriesInSuite = jest.fn();
@@ -28,13 +29,13 @@ describe("queryResolver", () => {
beforeEach(() => {
getQlPackForDbschemeSpy = jest
.spyOn(helpers, "getQlPackForDbscheme")
.spyOn(qlpack, "getQlPackForDbscheme")
.mockResolvedValue({
dbschemePack: "dbschemePack",
dbschemePackIsLibraryPack: false,
});
getPrimaryDbschemeSpy = jest
.spyOn(helpers, "getPrimaryDbscheme")
.spyOn(qlpack, "getPrimaryDbscheme")
.mockResolvedValue("primaryDbscheme");
jest.spyOn(helpers, "getOnDiskWorkspaceFolders").mockReturnValue([]);

View File

@@ -10,7 +10,8 @@ import { join } from "path";
import { ExtensionContext, Uri } from "vscode";
import { DatabaseManager } from "../../../../src/databases/local-databases";
import { tmpDir, walkDirectory } from "../../../../src/helpers";
import { tmpDir } from "../../../../src/helpers";
import { walkDirectory } from "../../../../src/pure/files";
import { DisposableBucket } from "../../disposable-bucket";
import { testDisposeHandler } from "../../test-dispose-handler";
import { HistoryItemLabelProvider } from "../../../../src/query-history/history-item-label-provider";