Skip to content

Commit 32656c1

Browse files
committed
Extract query resolving to more generic functions
1 parent ef27730 commit 32656c1

File tree

8 files changed

+268
-147
lines changed

8 files changed

+268
-147
lines changed

extensions/ql-vscode/src/data-extensions-editor/generate-flow-model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { QueryResultType } from "../query-server/new-messages";
1414
import { file } from "tmp-promise";
1515
import { writeFile } from "fs-extra";
1616
import { dump } from "js-yaml";
17-
import { qlpackOfDatabase } from "../language-support";
17+
import { qlpackOfDatabase } from "../local-queries";
1818
import { telemetryListener } from "../common/vscode/telemetry";
1919

2020
type FlowModelOptions = {

extensions/ql-vscode/src/language-support/contextual/location-finder.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,13 @@ import { CodeQLCliServer } from "../../codeql-cli/cli";
1212
import { DatabaseManager, DatabaseItem } from "../../databases/local-databases";
1313
import { ProgressCallback } from "../../common/vscode/progress";
1414
import { KeyType } from "./key-type";
15-
import {
16-
qlpackOfDatabase,
17-
resolveQueries,
18-
runContextualQuery,
19-
} from "./query-resolver";
15+
import { resolveQueries, runContextualQuery } from "./query-resolver";
2016
import { CancellationToken, LocationLink, Uri } from "vscode";
2117
import { QueryOutputDir } from "../../run-queries-shared";
2218
import { QueryRunner } from "../../query-server";
2319
import { QueryResultType } from "../../query-server/new-messages";
2420
import { fileRangeFromURI } from "./file-range-from-uri";
21+
import { qlpackOfDatabase } from "../../local-queries";
2522

2623
export const SELECT_QUERY_NAME = "#select";
2724
export const SELECTED_SOURCE_FILE = "selectedSourceFile";

extensions/ql-vscode/src/language-support/contextual/query-resolver.ts

Lines changed: 8 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
1-
import { writeFile, promises } from "fs-extra";
2-
import { dump } from "js-yaml";
3-
import { file } from "tmp-promise";
1+
import { promises } from "fs-extra";
42
import { basename, dirname, resolve } from "path";
53

64
import { getOnDiskWorkspaceFolders } from "../../common/vscode/workspace-folders";
7-
import {
8-
getPrimaryDbscheme,
9-
getQlPackForDbscheme,
10-
QlPacksForLanguage,
11-
} from "../../databases/qlpack";
5+
import { QlPacksForLanguage } from "../../databases/qlpack";
126
import {
137
KeyType,
148
kindOfKeyType,
@@ -17,97 +11,23 @@ import {
1711
} from "./key-type";
1812
import { CodeQLCliServer } from "../../codeql-cli/cli";
1913
import { DatabaseItem } from "../../databases/local-databases";
14+
import { resolveQueries as resolveLocalQueries } from "../../local-queries/query-resolver";
2015
import { extLogger } from "../../common/logging/vscode";
21-
import {
22-
showAndLogExceptionWithTelemetry,
23-
TeeLogger,
24-
} from "../../common/logging";
16+
import { TeeLogger } from "../../common/logging";
2517
import { CancellationToken } from "vscode";
2618
import { ProgressCallback } from "../../common/vscode/progress";
2719
import { CoreCompletedQuery, QueryRunner } from "../../query-server";
28-
import { redactableError } from "../../common/errors";
2920
import { QLPACK_FILENAMES } from "../../common/ql";
30-
import { telemetryListener } from "../../common/vscode/telemetry";
31-
32-
export async function qlpackOfDatabase(
33-
cli: Pick<CodeQLCliServer, "resolveQlpacks">,
34-
db: Pick<DatabaseItem, "contents">,
35-
): Promise<QlPacksForLanguage> {
36-
if (db.contents === undefined) {
37-
throw new Error("Database is invalid and cannot infer QLPack.");
38-
}
39-
const datasetPath = db.contents.datasetUri.fsPath;
40-
const dbscheme = await getPrimaryDbscheme(datasetPath);
41-
return await getQlPackForDbscheme(cli, dbscheme);
42-
}
43-
44-
/**
45-
* Finds the contextual queries with the specified key in a list of CodeQL packs.
46-
*
47-
* @param cli The CLI instance to use.
48-
* @param qlpacks The list of packs to search.
49-
* @param keyType The contextual query key of the query to search for.
50-
* @returns The found queries from the first pack in which any matching queries were found.
51-
*/
52-
async function resolveQueriesFromPacks(
53-
cli: CodeQLCliServer,
54-
qlpacks: string[],
55-
keyType: KeyType,
56-
): Promise<string[]> {
57-
const suiteFile = (
58-
await file({
59-
postfix: ".qls",
60-
})
61-
).path;
62-
const suiteYaml = [];
63-
for (const qlpack of qlpacks) {
64-
suiteYaml.push({
65-
from: qlpack,
66-
queries: ".",
67-
include: {
68-
kind: kindOfKeyType(keyType),
69-
"tags contain": tagOfKeyType(keyType),
70-
},
71-
});
72-
}
73-
await writeFile(suiteFile, dump(suiteYaml), "utf8");
74-
75-
const queries = await cli.resolveQueriesInSuite(
76-
suiteFile,
77-
getOnDiskWorkspaceFolders(),
78-
);
79-
return queries;
80-
}
8121

8222
export async function resolveQueries(
8323
cli: CodeQLCliServer,
8424
qlpacks: QlPacksForLanguage,
8525
keyType: KeyType,
8626
): Promise<string[]> {
87-
const packsToSearch: string[] = [];
88-
89-
// The CLI can handle both library packs and query packs, so search both packs in order.
90-
packsToSearch.push(qlpacks.dbschemePack);
91-
if (qlpacks.queryPack !== undefined) {
92-
packsToSearch.push(qlpacks.queryPack);
93-
}
94-
95-
const queries = await resolveQueriesFromPacks(cli, packsToSearch, keyType);
96-
if (queries.length > 0) {
97-
return queries;
98-
}
99-
100-
// No queries found. Determine the correct error message for the various scenarios.
101-
const keyTypeName = nameOfKeyType(keyType);
102-
const keyTypeTag = tagOfKeyType(keyType);
103-
const joinedPacksToSearch = packsToSearch.join(", ");
104-
const error = redactableError`No ${keyTypeName} queries (tagged "${keyTypeTag}") could be found in the \
105-
current library path (tried searching the following packs: ${joinedPacksToSearch}). \
106-
Try upgrading the CodeQL libraries. If that doesn't work, then ${keyTypeName} queries are not yet available \
107-
for this language.`;
108-
109-
void showAndLogExceptionWithTelemetry(extLogger, telemetryListener, error);
110-
throw error;
27+
return resolveLocalQueries(cli, qlpacks, nameOfKeyType(keyType), {
28+
kind: kindOfKeyType(keyType),
29+
"tags contain": [tagOfKeyType(keyType)],
30+
});
11131
}
11232

11333
async function resolveContextualQuery(

extensions/ql-vscode/src/language-support/contextual/template-provider.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,15 @@ import {
2727
SELECTED_SOURCE_LINE,
2828
SELECTED_SOURCE_COLUMN,
2929
} from "./location-finder";
30-
import {
31-
qlpackOfDatabase,
32-
resolveQueries,
33-
runContextualQuery,
34-
} from "./query-resolver";
30+
import { resolveQueries, runContextualQuery } from "./query-resolver";
3531
import {
3632
isCanary,
3733
NO_CACHE_AST_VIEWER,
3834
NO_CACHE_CONTEXTUAL_QUERIES,
3935
} from "../../config";
4036
import { CoreCompletedQuery, QueryRunner } from "../../query-server";
4137
import { AstBuilder } from "../ast-viewer/ast-builder";
38+
import { qlpackOfDatabase } from "../../local-queries";
4239

4340
/**
4441
* Runs templated CodeQL queries to find definitions in

extensions/ql-vscode/src/local-queries/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from "./local-queries";
22
export * from "./local-query-run";
3+
export * from "./query-resolver";
34
export * from "./quick-eval-code-lens-provider";
45
export * from "./quick-query";
56
export * from "./results-view";
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { CodeQLCliServer } from "../codeql-cli/cli";
2+
import { DatabaseItem } from "../databases/local-databases";
3+
import {
4+
getPrimaryDbscheme,
5+
getQlPackForDbscheme,
6+
QlPacksForLanguage,
7+
} from "../databases/qlpack";
8+
import { file } from "tmp-promise";
9+
import { writeFile } from "fs-extra";
10+
import { dump } from "js-yaml";
11+
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
12+
import { redactableError } from "../common/errors";
13+
import { showAndLogExceptionWithTelemetry } from "../common/logging";
14+
import { extLogger } from "../common/logging/vscode";
15+
import { telemetryListener } from "../common/vscode/telemetry";
16+
17+
export async function qlpackOfDatabase(
18+
cli: Pick<CodeQLCliServer, "resolveQlpacks">,
19+
db: Pick<DatabaseItem, "contents">,
20+
): Promise<QlPacksForLanguage> {
21+
if (db.contents === undefined) {
22+
throw new Error("Database is invalid and cannot infer QLPack.");
23+
}
24+
const datasetPath = db.contents.datasetUri.fsPath;
25+
const dbscheme = await getPrimaryDbscheme(datasetPath);
26+
return await getQlPackForDbscheme(cli, dbscheme);
27+
}
28+
29+
export interface QueryConstraints {
30+
kind?: string;
31+
"tags contain"?: string[];
32+
"tags contain all"?: string[];
33+
}
34+
35+
/**
36+
* Finds the queries with the specified kind and tags in a list of CodeQL packs.
37+
*
38+
* @param cli The CLI instance to use.
39+
* @param qlpacks The list of packs to search.
40+
* @param constraints Constraints on the queries to search for.
41+
* @returns The found queries from the first pack in which any matching queries were found.
42+
*/
43+
async function resolveQueriesFromPacks(
44+
cli: CodeQLCliServer,
45+
qlpacks: string[],
46+
constraints: QueryConstraints,
47+
): Promise<string[]> {
48+
const suiteFile = (
49+
await file({
50+
postfix: ".qls",
51+
})
52+
).path;
53+
const suiteYaml = [];
54+
for (const qlpack of qlpacks) {
55+
suiteYaml.push({
56+
from: qlpack,
57+
queries: ".",
58+
include: constraints,
59+
});
60+
}
61+
await writeFile(
62+
suiteFile,
63+
dump(suiteYaml, {
64+
noRefs: true, // CodeQL doesn't really support refs
65+
}),
66+
"utf8",
67+
);
68+
69+
return await cli.resolveQueriesInSuite(
70+
suiteFile,
71+
getOnDiskWorkspaceFolders(),
72+
);
73+
}
74+
75+
/**
76+
* Finds the queries with the specified kind and tags in a QLPack.
77+
*
78+
* @param cli The CLI instance to use.
79+
* @param qlpacks The list of packs to search.
80+
* @param name The name of the query to use in error messages.
81+
* @param constraints Constraints on the queries to search for.
82+
* @returns The found queries from the first pack in which any matching queries were found.
83+
*/
84+
export async function resolveQueries(
85+
cli: CodeQLCliServer,
86+
qlpacks: QlPacksForLanguage,
87+
name: string,
88+
constraints: QueryConstraints,
89+
): Promise<string[]> {
90+
const packsToSearch: string[] = [];
91+
92+
// The CLI can handle both library packs and query packs, so search both packs in order.
93+
packsToSearch.push(qlpacks.dbschemePack);
94+
if (qlpacks.queryPack !== undefined) {
95+
packsToSearch.push(qlpacks.queryPack);
96+
}
97+
98+
const queries = await resolveQueriesFromPacks(
99+
cli,
100+
packsToSearch,
101+
constraints,
102+
);
103+
if (queries.length > 0) {
104+
return queries;
105+
}
106+
107+
// No queries found. Determine the correct error message for the various scenarios.
108+
const humanConstraints = [];
109+
if (constraints.kind !== undefined) {
110+
humanConstraints.push(`kind "${constraints.kind}"`);
111+
}
112+
if (constraints["tags contain"] !== undefined) {
113+
humanConstraints.push(`tagged "${constraints["tags contain"].join(" ")}"`);
114+
}
115+
if (constraints["tags contain all"] !== undefined) {
116+
humanConstraints.push(
117+
`tagged all of "${constraints["tags contain all"].join(" ")}"`,
118+
);
119+
}
120+
121+
const joinedPacksToSearch = packsToSearch.join(", ");
122+
const error = redactableError`No ${name} queries (${humanConstraints.join(
123+
", ",
124+
)}) could be found in the \
125+
current library path (tried searching the following packs: ${joinedPacksToSearch}). \
126+
Try upgrading the CodeQL libraries. If that doesn't work, then ${name} queries are not yet available \
127+
for this language.`;
128+
129+
void showAndLogExceptionWithTelemetry(extLogger, telemetryListener, error);
130+
throw error;
131+
}

extensions/ql-vscode/test/vscode-tests/no-workspace/language-support/contextual/query-resolver.test.ts

Lines changed: 4 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,18 @@ import { getErrorMessage } from "../../../../../src/common/helpers-pure";
55

66
import * as log from "../../../../../src/common/logging/notifications";
77
import * as workspaceFolders from "../../../../../src/common/vscode/workspace-folders";
8-
import * as qlpack from "../../../../../src/databases/qlpack";
9-
import {
10-
KeyType,
11-
qlpackOfDatabase,
12-
resolveQueries,
13-
} from "../../../../../src/language-support";
8+
import { KeyType, resolveQueries } from "../../../../../src/language-support";
149
import { CodeQLCliServer } from "../../../../../src/codeql-cli/cli";
15-
import { mockDatabaseItem, mockedObject } from "../../../utils/mocking.helpers";
10+
import { mockedObject } from "../../../utils/mocking.helpers";
1611

1712
describe("queryResolver", () => {
18-
let getQlPackForDbschemeSpy: jest.SpiedFunction<
19-
typeof qlpack.getQlPackForDbscheme
20-
>;
21-
let getPrimaryDbschemeSpy: jest.SpiedFunction<
22-
typeof qlpack.getPrimaryDbscheme
23-
>;
24-
2513
const resolveQueriesInSuite = jest.fn();
2614

2715
const mockCli = mockedObject<CodeQLCliServer>({
2816
resolveQueriesInSuite,
2917
});
3018

3119
beforeEach(() => {
32-
getQlPackForDbschemeSpy = jest
33-
.spyOn(qlpack, "getQlPackForDbscheme")
34-
.mockResolvedValue({
35-
dbschemePack: "dbschemePack",
36-
dbschemePackIsLibraryPack: false,
37-
});
38-
getPrimaryDbschemeSpy = jest
39-
.spyOn(qlpack, "getPrimaryDbscheme")
40-
.mockResolvedValue("primaryDbscheme");
41-
4220
jest
4321
.spyOn(workspaceFolders, "getOnDiskWorkspaceFolders")
4422
.mockReturnValue([]);
@@ -68,7 +46,7 @@ describe("queryResolver", () => {
6846
queries: ".",
6947
include: {
7048
kind: "definitions",
71-
"tags contain": "ide-contextual-queries/local-definitions",
49+
"tags contain": ["ide-contextual-queries/local-definitions"],
7250
},
7351
},
7452
]);
@@ -87,31 +65,9 @@ describe("queryResolver", () => {
8765
expect(true).toBe(false);
8866
} catch (e) {
8967
expect(getErrorMessage(e)).toBe(
90-
'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.',
68+
'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.',
9169
);
9270
}
9371
});
9472
});
95-
96-
describe("qlpackOfDatabase", () => {
97-
it("should get the qlpack of a database", async () => {
98-
getQlPackForDbschemeSpy.mockResolvedValue({
99-
dbschemePack: "my-qlpack",
100-
dbschemePackIsLibraryPack: false,
101-
});
102-
const db = mockDatabaseItem({
103-
contents: {
104-
datasetUri: {
105-
fsPath: "/path/to/database",
106-
},
107-
},
108-
});
109-
const result = await qlpackOfDatabase(mockCli, db);
110-
expect(result).toEqual({
111-
dbschemePack: "my-qlpack",
112-
dbschemePackIsLibraryPack: false,
113-
});
114-
expect(getPrimaryDbschemeSpy).toBeCalledWith("/path/to/database");
115-
});
116-
});
11773
});

0 commit comments

Comments
 (0)