Extract query resolving to more generic functions

This commit is contained in:
Koen Vlaswinkel
2023-07-19 12:09:54 +02:00
parent ef27730e5e
commit 32656c1cb8
8 changed files with 268 additions and 147 deletions

View File

@@ -14,7 +14,7 @@ import { QueryResultType } from "../query-server/new-messages";
import { file } from "tmp-promise";
import { writeFile } from "fs-extra";
import { dump } from "js-yaml";
import { qlpackOfDatabase } from "../language-support";
import { qlpackOfDatabase } from "../local-queries";
import { telemetryListener } from "../common/vscode/telemetry";
type FlowModelOptions = {

View File

@@ -12,16 +12,13 @@ import { CodeQLCliServer } from "../../codeql-cli/cli";
import { DatabaseManager, DatabaseItem } from "../../databases/local-databases";
import { ProgressCallback } from "../../common/vscode/progress";
import { KeyType } from "./key-type";
import {
qlpackOfDatabase,
resolveQueries,
runContextualQuery,
} from "./query-resolver";
import { resolveQueries, runContextualQuery } from "./query-resolver";
import { CancellationToken, LocationLink, Uri } from "vscode";
import { QueryOutputDir } from "../../run-queries-shared";
import { QueryRunner } from "../../query-server";
import { QueryResultType } from "../../query-server/new-messages";
import { fileRangeFromURI } from "./file-range-from-uri";
import { qlpackOfDatabase } from "../../local-queries";
export const SELECT_QUERY_NAME = "#select";
export const SELECTED_SOURCE_FILE = "selectedSourceFile";

View File

@@ -1,14 +1,8 @@
import { writeFile, promises } from "fs-extra";
import { dump } from "js-yaml";
import { file } from "tmp-promise";
import { promises } from "fs-extra";
import { basename, dirname, resolve } from "path";
import { getOnDiskWorkspaceFolders } from "../../common/vscode/workspace-folders";
import {
getPrimaryDbscheme,
getQlPackForDbscheme,
QlPacksForLanguage,
} from "../../databases/qlpack";
import { QlPacksForLanguage } from "../../databases/qlpack";
import {
KeyType,
kindOfKeyType,
@@ -17,97 +11,23 @@ import {
} from "./key-type";
import { CodeQLCliServer } from "../../codeql-cli/cli";
import { DatabaseItem } from "../../databases/local-databases";
import { resolveQueries as resolveLocalQueries } from "../../local-queries/query-resolver";
import { extLogger } from "../../common/logging/vscode";
import {
showAndLogExceptionWithTelemetry,
TeeLogger,
} from "../../common/logging";
import { TeeLogger } from "../../common/logging";
import { CancellationToken } from "vscode";
import { ProgressCallback } from "../../common/vscode/progress";
import { CoreCompletedQuery, QueryRunner } from "../../query-server";
import { redactableError } from "../../common/errors";
import { QLPACK_FILENAMES } from "../../common/ql";
import { telemetryListener } from "../../common/vscode/telemetry";
export async function qlpackOfDatabase(
cli: Pick<CodeQLCliServer, "resolveQlpacks">,
db: Pick<DatabaseItem, "contents">,
): Promise<QlPacksForLanguage> {
if (db.contents === undefined) {
throw new Error("Database is invalid and cannot infer QLPack.");
}
const datasetPath = db.contents.datasetUri.fsPath;
const dbscheme = await getPrimaryDbscheme(datasetPath);
return await getQlPackForDbscheme(cli, dbscheme);
}
/**
* Finds the contextual queries with the specified key in a list of CodeQL packs.
*
* @param cli The CLI instance to use.
* @param qlpacks The list of packs to search.
* @param keyType The contextual query key of the query to search for.
* @returns The found queries from the first pack in which any matching queries were found.
*/
async function resolveQueriesFromPacks(
cli: CodeQLCliServer,
qlpacks: string[],
keyType: KeyType,
): Promise<string[]> {
const suiteFile = (
await file({
postfix: ".qls",
})
).path;
const suiteYaml = [];
for (const qlpack of qlpacks) {
suiteYaml.push({
from: qlpack,
queries: ".",
include: {
kind: kindOfKeyType(keyType),
"tags contain": tagOfKeyType(keyType),
},
});
}
await writeFile(suiteFile, dump(suiteYaml), "utf8");
const queries = await cli.resolveQueriesInSuite(
suiteFile,
getOnDiskWorkspaceFolders(),
);
return queries;
}
export async function resolveQueries(
cli: CodeQLCliServer,
qlpacks: QlPacksForLanguage,
keyType: KeyType,
): Promise<string[]> {
const packsToSearch: string[] = [];
// The CLI can handle both library packs and query packs, so search both packs in order.
packsToSearch.push(qlpacks.dbschemePack);
if (qlpacks.queryPack !== undefined) {
packsToSearch.push(qlpacks.queryPack);
}
const queries = await resolveQueriesFromPacks(cli, packsToSearch, keyType);
if (queries.length > 0) {
return queries;
}
// No queries found. Determine the correct error message for the various scenarios.
const keyTypeName = nameOfKeyType(keyType);
const keyTypeTag = tagOfKeyType(keyType);
const joinedPacksToSearch = packsToSearch.join(", ");
const error = redactableError`No ${keyTypeName} queries (tagged "${keyTypeTag}") could be found in the \
current library path (tried searching the following packs: ${joinedPacksToSearch}). \
Try upgrading the CodeQL libraries. If that doesn't work, then ${keyTypeName} queries are not yet available \
for this language.`;
void showAndLogExceptionWithTelemetry(extLogger, telemetryListener, error);
throw error;
return resolveLocalQueries(cli, qlpacks, nameOfKeyType(keyType), {
kind: kindOfKeyType(keyType),
"tags contain": [tagOfKeyType(keyType)],
});
}
async function resolveContextualQuery(

View File

@@ -27,11 +27,7 @@ import {
SELECTED_SOURCE_LINE,
SELECTED_SOURCE_COLUMN,
} from "./location-finder";
import {
qlpackOfDatabase,
resolveQueries,
runContextualQuery,
} from "./query-resolver";
import { resolveQueries, runContextualQuery } from "./query-resolver";
import {
isCanary,
NO_CACHE_AST_VIEWER,
@@ -39,6 +35,7 @@ import {
} from "../../config";
import { CoreCompletedQuery, QueryRunner } from "../../query-server";
import { AstBuilder } from "../ast-viewer/ast-builder";
import { qlpackOfDatabase } from "../../local-queries";
/**
* Runs templated CodeQL queries to find definitions in

View File

@@ -1,5 +1,6 @@
export * from "./local-queries";
export * from "./local-query-run";
export * from "./query-resolver";
export * from "./quick-eval-code-lens-provider";
export * from "./quick-query";
export * from "./results-view";

View File

@@ -0,0 +1,131 @@
import { CodeQLCliServer } from "../codeql-cli/cli";
import { DatabaseItem } from "../databases/local-databases";
import {
getPrimaryDbscheme,
getQlPackForDbscheme,
QlPacksForLanguage,
} from "../databases/qlpack";
import { file } from "tmp-promise";
import { writeFile } from "fs-extra";
import { dump } from "js-yaml";
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
import { redactableError } from "../common/errors";
import { showAndLogExceptionWithTelemetry } from "../common/logging";
import { extLogger } from "../common/logging/vscode";
import { telemetryListener } from "../common/vscode/telemetry";
export async function qlpackOfDatabase(
cli: Pick<CodeQLCliServer, "resolveQlpacks">,
db: Pick<DatabaseItem, "contents">,
): Promise<QlPacksForLanguage> {
if (db.contents === undefined) {
throw new Error("Database is invalid and cannot infer QLPack.");
}
const datasetPath = db.contents.datasetUri.fsPath;
const dbscheme = await getPrimaryDbscheme(datasetPath);
return await getQlPackForDbscheme(cli, dbscheme);
}
export interface QueryConstraints {
kind?: string;
"tags contain"?: string[];
"tags contain all"?: string[];
}
/**
* Finds the queries with the specified kind and tags in a list of CodeQL packs.
*
* @param cli The CLI instance to use.
* @param qlpacks The list of packs to search.
* @param constraints Constraints on the queries to search for.
* @returns The found queries from the first pack in which any matching queries were found.
*/
async function resolveQueriesFromPacks(
cli: CodeQLCliServer,
qlpacks: string[],
constraints: QueryConstraints,
): Promise<string[]> {
const suiteFile = (
await file({
postfix: ".qls",
})
).path;
const suiteYaml = [];
for (const qlpack of qlpacks) {
suiteYaml.push({
from: qlpack,
queries: ".",
include: constraints,
});
}
await writeFile(
suiteFile,
dump(suiteYaml, {
noRefs: true, // CodeQL doesn't really support refs
}),
"utf8",
);
return await cli.resolveQueriesInSuite(
suiteFile,
getOnDiskWorkspaceFolders(),
);
}
/**
* Finds the queries with the specified kind and tags in a QLPack.
*
* @param cli The CLI instance to use.
* @param qlpacks The list of packs to search.
* @param name The name of the query to use in error messages.
* @param constraints Constraints on the queries to search for.
* @returns The found queries from the first pack in which any matching queries were found.
*/
export async function resolveQueries(
cli: CodeQLCliServer,
qlpacks: QlPacksForLanguage,
name: string,
constraints: QueryConstraints,
): Promise<string[]> {
const packsToSearch: string[] = [];
// The CLI can handle both library packs and query packs, so search both packs in order.
packsToSearch.push(qlpacks.dbschemePack);
if (qlpacks.queryPack !== undefined) {
packsToSearch.push(qlpacks.queryPack);
}
const queries = await resolveQueriesFromPacks(
cli,
packsToSearch,
constraints,
);
if (queries.length > 0) {
return queries;
}
// No queries found. Determine the correct error message for the various scenarios.
const humanConstraints = [];
if (constraints.kind !== undefined) {
humanConstraints.push(`kind "${constraints.kind}"`);
}
if (constraints["tags contain"] !== undefined) {
humanConstraints.push(`tagged "${constraints["tags contain"].join(" ")}"`);
}
if (constraints["tags contain all"] !== undefined) {
humanConstraints.push(
`tagged all of "${constraints["tags contain all"].join(" ")}"`,
);
}
const joinedPacksToSearch = packsToSearch.join(", ");
const error = redactableError`No ${name} queries (${humanConstraints.join(
", ",
)}) could be found in the \
current library path (tried searching the following packs: ${joinedPacksToSearch}). \
Try upgrading the CodeQL libraries. If that doesn't work, then ${name} queries are not yet available \
for this language.`;
void showAndLogExceptionWithTelemetry(extLogger, telemetryListener, error);
throw error;
}

View File

@@ -5,23 +5,11 @@ import { getErrorMessage } from "../../../../../src/common/helpers-pure";
import * as log from "../../../../../src/common/logging/notifications";
import * as workspaceFolders from "../../../../../src/common/vscode/workspace-folders";
import * as qlpack from "../../../../../src/databases/qlpack";
import {
KeyType,
qlpackOfDatabase,
resolveQueries,
} from "../../../../../src/language-support";
import { KeyType, resolveQueries } from "../../../../../src/language-support";
import { CodeQLCliServer } from "../../../../../src/codeql-cli/cli";
import { mockDatabaseItem, mockedObject } from "../../../utils/mocking.helpers";
import { mockedObject } from "../../../utils/mocking.helpers";
describe("queryResolver", () => {
let getQlPackForDbschemeSpy: jest.SpiedFunction<
typeof qlpack.getQlPackForDbscheme
>;
let getPrimaryDbschemeSpy: jest.SpiedFunction<
typeof qlpack.getPrimaryDbscheme
>;
const resolveQueriesInSuite = jest.fn();
const mockCli = mockedObject<CodeQLCliServer>({
@@ -29,16 +17,6 @@ describe("queryResolver", () => {
});
beforeEach(() => {
getQlPackForDbschemeSpy = jest
.spyOn(qlpack, "getQlPackForDbscheme")
.mockResolvedValue({
dbschemePack: "dbschemePack",
dbschemePackIsLibraryPack: false,
});
getPrimaryDbschemeSpy = jest
.spyOn(qlpack, "getPrimaryDbscheme")
.mockResolvedValue("primaryDbscheme");
jest
.spyOn(workspaceFolders, "getOnDiskWorkspaceFolders")
.mockReturnValue([]);
@@ -68,7 +46,7 @@ describe("queryResolver", () => {
queries: ".",
include: {
kind: "definitions",
"tags contain": "ide-contextual-queries/local-definitions",
"tags contain": ["ide-contextual-queries/local-definitions"],
},
},
]);
@@ -87,31 +65,9 @@ describe("queryResolver", () => {
expect(true).toBe(false);
} catch (e) {
expect(getErrorMessage(e)).toBe(
'No definitions queries (tagged "ide-contextual-queries/local-definitions") could be found in the current library path (tried searching the following packs: my-qlpack). Try upgrading the CodeQL libraries. If that doesn\'t work, then definitions queries are not yet available for this language.',
'No definitions queries (kind "definitions", tagged "ide-contextual-queries/local-definitions") could be found in the current library path (tried searching the following packs: my-qlpack). Try upgrading the CodeQL libraries. If that doesn\'t work, then definitions queries are not yet available for this language.',
);
}
});
});
describe("qlpackOfDatabase", () => {
it("should get the qlpack of a database", async () => {
getQlPackForDbschemeSpy.mockResolvedValue({
dbschemePack: "my-qlpack",
dbschemePackIsLibraryPack: false,
});
const db = mockDatabaseItem({
contents: {
datasetUri: {
fsPath: "/path/to/database",
},
},
});
const result = await qlpackOfDatabase(mockCli, db);
expect(result).toEqual({
dbschemePack: "my-qlpack",
dbschemePackIsLibraryPack: false,
});
expect(getPrimaryDbschemeSpy).toBeCalledWith("/path/to/database");
});
});
});

View File

@@ -0,0 +1,119 @@
import {
qlpackOfDatabase,
resolveQueries,
} from "../../../../src/local-queries";
import { mockDatabaseItem, mockedObject } from "../../utils/mocking.helpers";
import { CodeQLCliServer } from "../../../../src/codeql-cli/cli";
import * as qlpack from "../../../../src/databases/qlpack";
import * as workspaceFolders from "../../../../src/common/vscode/workspace-folders";
import * as log from "../../../../src/common/logging/notifications";
import { load } from "js-yaml";
import * as fs from "fs-extra";
describe("qlpackOfDatabase", () => {
let getQlPackForDbschemeSpy: jest.SpiedFunction<
typeof qlpack.getQlPackForDbscheme
>;
let getPrimaryDbschemeSpy: jest.SpiedFunction<
typeof qlpack.getPrimaryDbscheme
>;
const mockCli = mockedObject<CodeQLCliServer>({});
beforeEach(() => {
getQlPackForDbschemeSpy = jest
.spyOn(qlpack, "getQlPackForDbscheme")
.mockResolvedValue({
dbschemePack: "dbschemePack",
dbschemePackIsLibraryPack: false,
});
getPrimaryDbschemeSpy = jest
.spyOn(qlpack, "getPrimaryDbscheme")
.mockResolvedValue("primaryDbscheme");
});
it("should get the qlpack of a database", async () => {
getQlPackForDbschemeSpy.mockResolvedValue({
dbschemePack: "my-qlpack",
dbschemePackIsLibraryPack: false,
});
const db = mockDatabaseItem({
contents: {
datasetUri: {
fsPath: "/path/to/database",
},
},
});
const result = await qlpackOfDatabase(mockCli, db);
expect(result).toEqual({
dbschemePack: "my-qlpack",
dbschemePackIsLibraryPack: false,
});
expect(getPrimaryDbschemeSpy).toBeCalledWith("/path/to/database");
});
});
describe("resolveQueries", () => {
const resolveQueriesInSuite = jest.fn();
const mockCli = mockedObject<CodeQLCliServer>({
resolveQueriesInSuite,
});
beforeEach(() => {
jest
.spyOn(workspaceFolders, "getOnDiskWorkspaceFolders")
.mockReturnValue([]);
jest.spyOn(log, "showAndLogErrorMessage").mockResolvedValue(undefined);
});
it("should resolve a query", async () => {
resolveQueriesInSuite.mockReturnValue(["a", "b"]);
const result = await resolveQueries(
mockCli,
{ dbschemePack: "my-qlpack", dbschemePackIsLibraryPack: false },
"my query",
{
kind: "graph",
"tags contain": ["ide-contextual-queries/print-ast"],
},
);
expect(result).toEqual(["a", "b"]);
expect(resolveQueriesInSuite).toHaveBeenCalledWith(
expect.stringMatching(/\.qls$/),
[],
);
const fileName = resolveQueriesInSuite.mock.calls[0][0];
expect(load(await fs.readFile(fileName, "utf-8"))).toEqual([
{
from: "my-qlpack",
queries: ".",
include: {
kind: "graph",
"tags contain": ["ide-contextual-queries/print-ast"],
},
},
]);
});
it("should throw an error when there are no queries found", async () => {
resolveQueriesInSuite.mockReturnValue([]);
await expect(
resolveQueries(
mockCli,
{ dbschemePack: "my-qlpack", dbschemePackIsLibraryPack: false },
"my query",
{
kind: "graph",
"tags contain": ["ide-contextual-queries/print-ast"],
},
),
).rejects.toThrowError(
'No my query queries (kind "graph", tagged "ide-contextual-queries/print-ast") could be found in the current library path (tried searching the following packs: my-qlpack). Try upgrading the CodeQL libraries. If that doesn\'t work, then my query queries are not yet available for this language.',
);
});
});