Skip to content

Commit 83cc983

Browse files
authored
Merge pull request #2634 from github/koesie10/cleanup-query-resolver
Make query resolver more generic
2 parents b87fe94 + 57bcfbb commit 83cc983

10 files changed

Lines changed: 459 additions & 214 deletions

File tree

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";
Lines changed: 9 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
1-
import { writeFile, promises } from "fs-extra";
2-
import { dump } from "js-yaml";
3-
import { file } from "tmp-promise";
4-
import { basename, dirname, resolve } from "path";
5-
61
import { getOnDiskWorkspaceFolders } from "../../common/vscode/workspace-folders";
7-
import {
8-
getPrimaryDbscheme,
9-
getQlPackForDbscheme,
10-
QlPacksForLanguage,
11-
} from "../../databases/qlpack";
2+
import { QlPacksForLanguage } from "../../databases/qlpack";
123
import {
134
KeyType,
145
kindOfKeyType,
@@ -17,154 +8,22 @@ import {
178
} from "./key-type";
189
import { CodeQLCliServer } from "../../codeql-cli/cli";
1910
import { DatabaseItem } from "../../databases/local-databases";
11+
import { resolveQueries as resolveLocalQueries } from "../../local-queries/query-resolver";
2012
import { extLogger } from "../../common/logging/vscode";
21-
import {
22-
showAndLogExceptionWithTelemetry,
23-
TeeLogger,
24-
} from "../../common/logging";
13+
import { TeeLogger } from "../../common/logging";
2514
import { CancellationToken } from "vscode";
2615
import { ProgressCallback } from "../../common/vscode/progress";
2716
import { CoreCompletedQuery, QueryRunner } from "../../query-server";
28-
import { redactableError } from "../../common/errors";
29-
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-
}
17+
import { createLockFileForStandardQuery } from "../../local-queries/standard-queries";
8118

8219
export async function resolveQueries(
8320
cli: CodeQLCliServer,
8421
qlpacks: QlPacksForLanguage,
8522
keyType: KeyType,
8623
): 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;
111-
}
112-
113-
async function resolveContextualQuery(
114-
cli: CodeQLCliServer,
115-
query: string,
116-
): Promise<{ packPath: string; createdTempLockFile: boolean }> {
117-
// Contextual queries now live within the standard library packs.
118-
// This simplifies distribution (you don't need the standard query pack to use the AST viewer),
119-
// but if the library pack doesn't have a lockfile, we won't be able to find
120-
// other pack dependencies of the library pack.
121-
122-
// Work out the enclosing pack.
123-
const packContents = await cli.packPacklist(query, false);
124-
const packFilePath = packContents.find((p) =>
125-
QLPACK_FILENAMES.includes(basename(p)),
126-
);
127-
if (packFilePath === undefined) {
128-
// Should not happen; we already resolved this query.
129-
throw new Error(
130-
`Could not find a CodeQL pack file for the pack enclosing the contextual query ${query}`,
131-
);
132-
}
133-
const packPath = dirname(packFilePath);
134-
const lockFilePath = packContents.find((p) =>
135-
["codeql-pack.lock.yml", "qlpack.lock.yml"].includes(basename(p)),
136-
);
137-
let createdTempLockFile = false;
138-
if (!lockFilePath) {
139-
// No lock file, likely because this library pack is in the package cache.
140-
// Create a lock file so that we can resolve dependencies and library path
141-
// for the contextual query.
142-
void extLogger.log(
143-
`Library pack ${packPath} is missing a lock file; creating a temporary lock file`,
144-
);
145-
await cli.packResolveDependencies(packPath);
146-
createdTempLockFile = true;
147-
// Clear CLI server pack cache before installing dependencies,
148-
// so that it picks up the new lock file, not the previously cached pack.
149-
void extLogger.log("Clearing the CodeQL CLI server's pack cache");
150-
await cli.clearCache();
151-
// Install dependencies.
152-
void extLogger.log(
153-
`Installing package dependencies for library pack ${packPath}`,
154-
);
155-
await cli.packInstall(packPath);
156-
}
157-
return { packPath, createdTempLockFile };
158-
}
159-
160-
async function removeTemporaryLockFile(packPath: string) {
161-
const tempLockFilePath = resolve(packPath, "codeql-pack.lock.yml");
162-
void extLogger.log(
163-
`Deleting temporary package lock file at ${tempLockFilePath}`,
164-
);
165-
// It's fine if the file doesn't exist.
166-
await promises.rm(resolve(packPath, "codeql-pack.lock.yml"), {
167-
force: true,
24+
return resolveLocalQueries(cli, qlpacks, nameOfKeyType(keyType), {
25+
kind: kindOfKeyType(keyType),
26+
"tags contain": [tagOfKeyType(keyType)],
16827
});
16928
}
17029

@@ -178,10 +37,7 @@ export async function runContextualQuery(
17837
token: CancellationToken,
17938
templates: Record<string, string>,
18039
): Promise<CoreCompletedQuery> {
181-
const { packPath, createdTempLockFile } = await resolveContextualQuery(
182-
cli,
183-
query,
184-
);
40+
const { cleanup } = await createLockFileForStandardQuery(cli, query);
18541
const queryRun = qs.createQueryRun(
18642
db.databaseUri.fsPath,
18743
{ queryPath: query, quickEvalPosition: undefined },
@@ -200,8 +56,6 @@ export async function runContextualQuery(
20056
token,
20157
new TeeLogger(qs.logger, queryRun.outputDir.logPath),
20258
);
203-
if (createdTempLockFile) {
204-
await removeTemporaryLockFile(packPath);
205-
}
59+
await cleanup?.();
20660
return results;
20761
}

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+
}

0 commit comments

Comments
 (0)