Skip to content

Commit e9b67dd

Browse files
committed
Resolve model editor queries from ql if present
1 parent b1debee commit e9b67dd

File tree

8 files changed

+215
-47
lines changed

8 files changed

+215
-47
lines changed

extensions/ql-vscode/src/local-queries/query-resolver.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@ export interface QueryConstraints {
3939
* @param cli The CLI instance to use.
4040
* @param qlpacks The list of packs to search.
4141
* @param constraints Constraints on the queries to search for.
42+
* @param additionalPacks Additional pack paths to search.
4243
* @returns The found queries from the first pack in which any matching queries were found.
4344
*/
4445
async function resolveQueriesFromPacks(
4546
cli: CodeQLCliServer,
4647
qlpacks: string[],
4748
constraints: QueryConstraints,
49+
additionalPacks: string[] = [],
4850
): Promise<string[]> {
4951
const suiteFile = (
5052
await file({
@@ -67,10 +69,10 @@ async function resolveQueriesFromPacks(
6769
"utf8",
6870
);
6971

70-
return await cli.resolveQueriesInSuite(
71-
suiteFile,
72-
getOnDiskWorkspaceFolders(),
73-
);
72+
return await cli.resolveQueriesInSuite(suiteFile, [
73+
...getOnDiskWorkspaceFolders(),
74+
...additionalPacks,
75+
]);
7476
}
7577

7678
export async function resolveQueriesByLanguagePack(
@@ -97,18 +99,23 @@ export async function resolveQueriesByLanguagePack(
9799
* @param packsToSearch The list of packs to search.
98100
* @param name The name of the query to use in error messages.
99101
* @param constraints Constraints on the queries to search for.
102+
* @param allowNoQueriesFound If true, will not throw an error if no queries are found.
103+
* @param additionalPacks Additional pack paths to search.
100104
* @returns The found queries from the first pack in which any matching queries were found.
101105
*/
102106
export async function resolveQueries(
103107
cli: CodeQLCliServer,
104108
packsToSearch: string[],
105109
name: string,
106110
constraints: QueryConstraints,
111+
allowNoQueriesFound = false,
112+
additionalPacks: string[] = [],
107113
): Promise<string[]> {
108114
const queries = await resolveQueriesFromPacks(
109115
cli,
110116
packsToSearch,
111117
constraints,
118+
additionalPacks,
112119
);
113120
if (queries.length > 0) {
114121
return queries;
@@ -128,6 +135,10 @@ export async function resolveQueries(
128135
);
129136
}
130137

138+
if (allowNoQueriesFound) {
139+
return [];
140+
}
141+
131142
const joinedPacksToSearch = packsToSearch.join(", ");
132143
const error = redactableError`No ${name} queries (${humanConstraints.join(
133144
", ",

extensions/ql-vscode/src/model-editor/auto-model-codeml-queries.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { Mode } from "./shared/mode";
77
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
88
import { interpretResultsSarif } from "../query-results";
99
import { join } from "path";
10-
import { assertNever } from "../common/helpers-pure";
1110
import { dir } from "tmp-promise";
1211
import { writeFile, outputFile } from "fs-extra";
1312
import { dump as dumpYaml } from "js-yaml";
@@ -16,17 +15,7 @@ import { runQuery } from "../local-queries/run-query";
1615
import { QueryMetadata } from "../common/interface-types";
1716
import { CancellationTokenSource } from "vscode";
1817
import { resolveQueries } from "../local-queries";
19-
20-
function modeTag(mode: Mode): string {
21-
switch (mode) {
22-
case Mode.Application:
23-
return "application-mode";
24-
case Mode.Framework:
25-
return "framework-mode";
26-
default:
27-
assertNever(mode);
28-
}
29-
}
18+
import { modeTag } from "./mode-tag";
3019

3120
type AutoModelQueriesOptions = {
3221
mode: Mode;

extensions/ql-vscode/src/model-editor/external-api-usage-queries.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import { fetchExternalApiQueries } from "./queries";
1616
import { Method } from "./method";
1717
import { runQuery } from "../local-queries/run-query";
1818
import { decodeBqrsToMethods } from "./bqrs";
19+
import {
20+
resolveEndpointsQuery,
21+
syntheticQueryPackName,
22+
} from "./model-editor-queries";
1923

2024
type RunQueryOptions = {
2125
cliServer: CodeQLCliServer;
@@ -88,7 +92,24 @@ export async function runExternalApiQueries(
8892
await cliServer.resolveQlpacks(additionalPacks, true),
8993
);
9094

91-
const queryPath = join(queryDir, queryNameFromMode(mode));
95+
progress({
96+
message: "Resolving query",
97+
step: 2,
98+
maxStep: externalApiQueriesProgressMaxStep,
99+
});
100+
101+
// Resolve the queries from either codeql/java-queries or from the temporary queryDir
102+
const queryPath = await resolveEndpointsQuery(
103+
cliServer,
104+
databaseItem.language,
105+
mode,
106+
[syntheticQueryPackName],
107+
[queryDir],
108+
false,
109+
);
110+
if (!queryPath) {
111+
return;
112+
}
92113

93114
// Run the actual query
94115
const completedQuery = await runQuery({
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Mode } from "./shared/mode";
2+
import { assertNever } from "../common/helpers-pure";
3+
4+
export function modeTag(mode: Mode): string {
5+
switch (mode) {
6+
case Mode.Application:
7+
return "application-mode";
8+
case Mode.Framework:
9+
return "framework-mode";
10+
default:
11+
assertNever(mode);
12+
}
13+
}

extensions/ql-vscode/src/model-editor/model-editor-module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ export class ModelEditorModule extends DisposableObject {
132132
const { path: queryDir, cleanup: cleanupQueryDir } = await dir({
133133
unsafeCleanup: true,
134134
});
135+
135136
const success = await setUpPack(this.cliServer, queryDir, language);
136137
if (!success) {
137138
await cleanupQueryDir();

extensions/ql-vscode/src/model-editor/model-editor-queries.ts

Lines changed: 116 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,28 @@ import { dump } from "js-yaml";
55
import { prepareExternalApiQuery } from "./external-api-usage-queries";
66
import { CodeQLCliServer } from "../codeql-cli/cli";
77
import { showLlmGeneration } from "../config";
8+
import { Mode } from "./shared/mode";
9+
import { resolveQueries } from "../local-queries";
10+
import { modeTag } from "./mode-tag";
11+
import { extLogger } from "../common/logging/vscode";
12+
13+
export const syntheticQueryPackName = "codeql/external-api-usage";
814

915
/**
10-
* setUpPack sets up a directory to use for the data extension editor queries.
16+
* setUpPack sets up a directory to use for the data extension editor queries if required.
17+
*
18+
* There are two cases (example language is Java):
19+
* - In case the queries are present in the codeql/java-queries, we don't need to write our own queries
20+
* to disk. We still need to create a synthetic query pack so we can pass the queryDir to the query
21+
* resolver without caring about whether the queries are present in the pack or not.
22+
* - In case the queries are not present in the codeql/java-queries, we need to write our own queries
23+
* to disk. We will create a synthetic query pack and install its dependencies so it is fully independent
24+
* and we can simply pass it through when resolving the queries.
25+
*
26+
* These steps together ensure that later steps of the process don't need to keep track of whether the queries
27+
* are present in codeql/java-queries or in our own query pack. They just need to resolve the query.
28+
*
29+
* @param cliServer The CodeQL CLI server to use.
1130
* @param queryDir The directory to set up.
1231
* @param language The language to use for the queries.
1332
* @returns true if the setup was successful, false otherwise.
@@ -17,34 +36,111 @@ export async function setUpPack(
1736
queryDir: string,
1837
language: QueryLanguage,
1938
): Promise<boolean> {
20-
// Create the external API query
21-
const externalApiQuerySuccess = await prepareExternalApiQuery(
22-
queryDir,
39+
// Download the required query packs
40+
await cliServer.packDownload([`codeql/${language}-queries`]);
41+
42+
const applicationModeQuery = await resolveEndpointsQuery(
43+
cliServer,
2344
language,
45+
Mode.Application,
46+
[],
47+
[],
48+
true,
2449
);
25-
if (!externalApiQuerySuccess) {
26-
return false;
27-
}
2850

29-
// Set up a synthetic pack so that the query can be resolved later.
30-
const syntheticQueryPack = {
31-
name: "codeql/external-api-usage",
32-
version: "0.0.0",
33-
dependencies: {
34-
[`codeql/${language}-all`]: "*",
35-
},
36-
};
51+
if (applicationModeQuery) {
52+
void extLogger.log("Using application mode queries");
3753

38-
const qlpackFile = join(queryDir, "codeql-pack.yml");
39-
await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8");
40-
await cliServer.packInstall(queryDir);
54+
// Set up a synthetic pack so CodeQL doesn't crash later when we try
55+
// to resolve a query within this directory
56+
const syntheticQueryPack = {
57+
name: syntheticQueryPackName,
58+
version: "0.0.0",
59+
dependencies: {},
60+
};
4161

42-
// Install the other needed query packs
43-
await cliServer.packDownload([`codeql/${language}-queries`]);
62+
const qlpackFile = join(queryDir, "codeql-pack.yml");
63+
await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8");
64+
} else {
65+
void extLogger.log("Writing external API usage queries to disk");
4466

67+
// If we can't resolve the query, we need to write them to desk ourselves.
68+
const externalApiQuerySuccess = await prepareExternalApiQuery(
69+
queryDir,
70+
language,
71+
);
72+
if (!externalApiQuerySuccess) {
73+
return false;
74+
}
75+
76+
// Set up a synthetic pack so that the query can be resolved later.
77+
const syntheticQueryPack = {
78+
name: syntheticQueryPackName,
79+
version: "0.0.0",
80+
dependencies: {
81+
[`codeql/${language}-all`]: "*",
82+
},
83+
};
84+
85+
const qlpackFile = join(queryDir, "codeql-pack.yml");
86+
await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8");
87+
await cliServer.packInstall(queryDir);
88+
}
89+
90+
// Download any other required packs
4591
if (language === "java" && showLlmGeneration()) {
4692
await cliServer.packDownload([`codeql/${language}-automodel-queries`]);
4793
}
4894

4995
return true;
5096
}
97+
98+
/**
99+
* Resolve the query path to the model editor endpoints query. All queries are tagged like this:
100+
* modeleditor endpoints <mode>
101+
* Example: modeleditor endpoints framework-mode
102+
*
103+
* @param cliServer The CodeQL CLI server to use.
104+
* @param language The language of the query pack to use.
105+
* @param mode The mode to resolve the query for.
106+
* @param additionalPackNames Additional pack names to search.
107+
* @param additionalPackPaths Additional pack paths to search.
108+
* @param allowNoQueriesFound If true, will not throw an error if no queries are found.
109+
*/
110+
export async function resolveEndpointsQuery(
111+
cliServer: CodeQLCliServer,
112+
language: string,
113+
mode: Mode,
114+
additionalPackNames: string[] = [],
115+
additionalPackPaths: string[] = [],
116+
allowNoQueriesFound = false,
117+
): Promise<string | undefined> {
118+
const packsToSearch = [`codeql/${language}-queries`, ...additionalPackNames];
119+
120+
// First, resolve the query that we want to run.
121+
// All queries are tagged like this:
122+
// internal extract automodel <mode> <queryTag>
123+
// Example: internal extract automodel framework-mode candidates
124+
const queries = await resolveQueries(
125+
cliServer,
126+
packsToSearch,
127+
`Fetch endpoints query for ${mode}`,
128+
{
129+
kind: "table",
130+
"tags contain all": ["modeleditor", "endpoints", modeTag(mode)],
131+
},
132+
allowNoQueriesFound,
133+
additionalPackPaths,
134+
);
135+
if (queries.length > 1) {
136+
throw new Error(
137+
`Found multiple endpoints queries for ${mode}. Can't continue`,
138+
);
139+
}
140+
141+
if (queries.length === 0) {
142+
return undefined;
143+
}
144+
145+
return queries[0];
146+
}

extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/external-api-usage-query.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ describe("external api usage query", () => {
4646
resolveQlpacks: jest.fn().mockResolvedValue({
4747
"my/extensions": "/a/b/c/",
4848
}),
49+
resolveQueriesInSuite: jest
50+
.fn()
51+
.mockResolvedValue(["/a/b/c/ApplicationModeEndpoints.ql"]),
4952
packPacklist: jest
5053
.fn()
5154
.mockResolvedValue([
@@ -104,6 +107,9 @@ describe("external api usage query", () => {
104107
resolveQlpacks: jest.fn().mockResolvedValue({
105108
"my/extensions": "/a/b/c/",
106109
}),
110+
resolveQueriesInSuite: jest
111+
.fn()
112+
.mockResolvedValue(["/a/b/c/ApplicationModeEndpoints.ql"]),
107113
packPacklist: jest
108114
.fn()
109115
.mockResolvedValue([

0 commit comments

Comments
 (0)