Skip to content

Commit 4096ded

Browse files
Merge branch 'main' into robertbrignull/automodel-sort-order
2 parents b59437e + fee7eae commit 4096ded

File tree

10 files changed

+311
-270
lines changed

10 files changed

+311
-270
lines changed

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

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,57 +5,6 @@ import type { AutoModelQueriesResult } from "./auto-model-codeml-queries";
55
import { assertNever } from "../common/helpers-pure";
66
import type { Log } from "sarif";
77
import { gzipEncode } from "../common/zlib";
8-
import type { Method, MethodSignature } from "./method";
9-
import type { ModeledMethod } from "./modeled-method";
10-
import { groupMethods, sortGroupNames, sortMethods } from "./shared/sorting";
11-
12-
/**
13-
* Return the candidates that the model should be run on. This includes limiting the number of
14-
* candidates to the candidate limit and filtering out anything that is already modeled and respecting
15-
* the order in the UI.
16-
* @param mode Whether it is application or framework mode.
17-
* @param methods all methods.
18-
* @param modeledMethodsBySignature the currently modeled methods.
19-
* @returns list of modeled methods that are candidates for modeling.
20-
*/
21-
export function getCandidates(
22-
mode: Mode,
23-
methods: readonly Method[],
24-
modeledMethodsBySignature: Record<string, readonly ModeledMethod[]>,
25-
processedByAutoModelMethods: Set<string>,
26-
): MethodSignature[] {
27-
const candidateMethods = methods.filter((method) => {
28-
// Filter out any methods already processed by auto-model
29-
if (processedByAutoModelMethods.has(method.signature)) {
30-
return false;
31-
}
32-
33-
const modeledMethods: ModeledMethod[] = [
34-
...(modeledMethodsBySignature[method.signature] ?? []),
35-
];
36-
37-
// Anything that is modeled is not a candidate
38-
if (modeledMethods.some((m) => m.type !== "none")) {
39-
return false;
40-
}
41-
42-
// A method that is supported is modeled outside of the model file, so it is not a candidate.
43-
if (method.supported) {
44-
return false;
45-
}
46-
47-
return true;
48-
});
49-
50-
// Sort the same way as the UI so we send the first ones listed in the UI first
51-
const grouped = groupMethods(candidateMethods, mode);
52-
const sortedGroupNames = sortGroupNames(grouped);
53-
return sortedGroupNames.flatMap((name) =>
54-
// We can safely pass empty sets for `modifiedSignatures` and `processedByAutoModelMethods`
55-
// because we've filtered out all methods that are already modeled or have already been processed by auto-model.
56-
sortMethods(grouped[name], modeledMethodsBySignature, new Set(), new Set()),
57-
);
58-
}
598

609
/**
6110
* Encode a SARIF log to the format expected by the server: JSON, GZIP-compressed, base64-encoded

extensions/ql-vscode/src/model-editor/auto-modeler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import type { ModeledMethod } from "./modeled-method";
33
import { load as loadYaml } from "js-yaml";
44
import type { ProgressCallback } from "../common/vscode/progress";
55
import { withProgress } from "../common/vscode/progress";
6-
import { createAutoModelRequest, getCandidates } from "./auto-model";
6+
import { createAutoModelRequest } from "./auto-model";
7+
import { getCandidates } from "./shared/auto-model-candidates";
78
import { runAutoModelQueries } from "./auto-model-codeml-queries";
89
import { loadDataExtensionYaml } from "./yaml";
910
import type { ModelRequest, ModelResponse } from "./auto-model-api";
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import type { Method, MethodSignature } from "../method";
2+
import type { ModeledMethod } from "../modeled-method";
3+
import type { Mode } from "./mode";
4+
import { groupMethods, sortGroupNames, sortMethods } from "./sorting";
5+
6+
/**
7+
* Return the candidates that the model should be run on. This includes limiting the number of
8+
* candidates to the candidate limit and filtering out anything that is already modeled and respecting
9+
* the order in the UI.
10+
* @param mode Whether it is application or framework mode.
11+
* @param methods all methods.
12+
* @param modeledMethodsBySignature the currently modeled methods.
13+
* @returns list of modeled methods that are candidates for modeling.
14+
*/
15+
16+
export function getCandidates(
17+
mode: Mode,
18+
methods: readonly Method[],
19+
modeledMethodsBySignature: Record<string, readonly ModeledMethod[]>,
20+
processedByAutoModelMethods: Set<string>,
21+
): MethodSignature[] {
22+
const candidateMethods = methods.filter((method) => {
23+
// Filter out any methods already processed by auto-model
24+
if (processedByAutoModelMethods.has(method.signature)) {
25+
return false;
26+
}
27+
28+
const modeledMethods: ModeledMethod[] = [
29+
...(modeledMethodsBySignature[method.signature] ?? []),
30+
];
31+
32+
// Anything that is modeled is not a candidate
33+
if (modeledMethods.some((m) => m.type !== "none")) {
34+
return false;
35+
}
36+
37+
// A method that is supported is modeled outside of the model file, so it is not a candidate.
38+
if (method.supported) {
39+
return false;
40+
}
41+
42+
return true;
43+
});
44+
45+
// Sort the same way as the UI so we send the first ones listed in the UI first
46+
const grouped = groupMethods(candidateMethods, mode);
47+
const sortedGroupNames = sortGroupNames(grouped);
48+
return sortedGroupNames.flatMap((name) =>
49+
// We can safely pass empty sets for `modifiedSignatures` and `processedByAutoModelMethods`
50+
// because we've filtered out all methods that are already modeled or have already been processed by auto-model.
51+
sortMethods(grouped[name], modeledMethodsBySignature, new Set(), new Set()),
52+
);
53+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { join } from "path";
2+
import type { BaseLogger } from "../common/logging";
3+
import type { QueryLanguage } from "../common/query-language";
4+
import type { CodeQLCliServer } from "../codeql-cli/cli";
5+
import type { QlPackDetails } from "./ql-pack-details";
6+
import { getQlPackFilePath } from "../common/ql";
7+
8+
export async function resolveCodeScanningQueryPack(
9+
logger: BaseLogger,
10+
cliServer: CodeQLCliServer,
11+
language: QueryLanguage,
12+
): Promise<QlPackDetails> {
13+
// Get pack
14+
void logger.log(`Downloading pack for language: ${language}`);
15+
const packName = `codeql/${language}-queries`;
16+
const packDownloadResult = await cliServer.packDownload([packName]);
17+
const downloadedPack = packDownloadResult.packs[0];
18+
19+
const packDir = join(
20+
packDownloadResult.packDir,
21+
downloadedPack.name,
22+
downloadedPack.version,
23+
);
24+
25+
// Resolve queries
26+
void logger.log(`Resolving queries for pack: ${packName}`);
27+
const suitePath = join(
28+
packDir,
29+
"codeql-suites",
30+
`${language}-code-scanning.qls`,
31+
);
32+
const resolvedQueries = await cliServer.resolveQueries(suitePath);
33+
34+
const problemQueries = await filterToOnlyProblemQueries(
35+
logger,
36+
cliServer,
37+
resolvedQueries,
38+
);
39+
40+
if (problemQueries.length === 0) {
41+
throw Error(
42+
`No problem queries found in published query pack: ${packName}.`,
43+
);
44+
}
45+
46+
// Return pack details
47+
const qlPackFilePath = await getQlPackFilePath(packDir);
48+
49+
const qlPackDetails: QlPackDetails = {
50+
queryFiles: problemQueries,
51+
qlPackRootPath: packDir,
52+
qlPackFilePath,
53+
language,
54+
};
55+
56+
return qlPackDetails;
57+
}
58+
59+
async function filterToOnlyProblemQueries(
60+
logger: BaseLogger,
61+
cliServer: CodeQLCliServer,
62+
queries: string[],
63+
): Promise<string[]> {
64+
const problemQueries: string[] = [];
65+
for (const query of queries) {
66+
const queryMetadata = await cliServer.resolveMetadata(query);
67+
if (
68+
queryMetadata.kind === "problem" ||
69+
queryMetadata.kind === "path-problem"
70+
) {
71+
problemQueries.push(query);
72+
} else {
73+
void logger.log(`Skipping non-problem query ${query}`);
74+
}
75+
}
76+
return problemQueries;
77+
}

extensions/ql-vscode/src/variant-analysis/variant-analysis-manager.ts

Lines changed: 8 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ import { getQlPackFilePath } from "../common/ql";
9494
import { tryGetQueryMetadata } from "../codeql-cli/query-metadata";
9595
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
9696
import { findVariantAnalysisQlPackRoot } from "./ql";
97+
import { resolveCodeScanningQueryPack } from "./code-scanning-pack";
9798

9899
const maxRetryCount = 3;
99100

@@ -219,7 +220,7 @@ export class VariantAnalysisManager
219220
public async runVariantAnalysisFromPublishedPack(): Promise<void> {
220221
return withProgress(async (progress, token) => {
221222
progress({
222-
maxStep: 8,
223+
maxStep: 7,
223224
step: 0,
224225
message: "Determining query language",
225226
});
@@ -230,53 +231,17 @@ export class VariantAnalysisManager
230231
}
231232

232233
progress({
233-
maxStep: 8,
234-
step: 1,
235-
message: "Downloading query pack",
236-
});
237-
238-
const packName = `codeql/${language}-queries`;
239-
const packDownloadResult = await this.cliServer.packDownload([packName]);
240-
const downloadedPack = packDownloadResult.packs[0];
241-
242-
const packDir = join(
243-
packDownloadResult.packDir,
244-
downloadedPack.name,
245-
downloadedPack.version,
246-
);
247-
248-
progress({
249-
maxStep: 8,
234+
maxStep: 7,
250235
step: 2,
251-
message: "Resolving queries in pack",
236+
message: "Downloading query pack and resolving queries",
252237
});
253238

254-
const suitePath = join(
255-
packDir,
256-
"codeql-suites",
257-
`${language}-code-scanning.qls`,
258-
);
259-
const resolvedQueries = await this.cliServer.resolveQueries(suitePath);
260-
261-
const problemQueries =
262-
await this.filterToOnlyProblemQueries(resolvedQueries);
263-
264-
if (problemQueries.length === 0) {
265-
void this.app.logger.showErrorMessage(
266-
`Unable to trigger variant analysis. No problem queries found in published query pack: ${packName}.`,
267-
);
268-
return;
269-
}
270-
271-
const qlPackFilePath = await getQlPackFilePath(packDir);
272-
273239
// Build up details to pass to the functions that run the variant analysis.
274-
const qlPackDetails: QlPackDetails = {
275-
queryFiles: problemQueries,
276-
qlPackRootPath: packDir,
277-
qlPackFilePath,
240+
const qlPackDetails = await resolveCodeScanningQueryPack(
241+
this.app.logger,
242+
this.cliServer,
278243
language,
279-
};
244+
);
280245

281246
await this.runVariantAnalysis(
282247
qlPackDetails,
@@ -291,24 +256,6 @@ export class VariantAnalysisManager
291256
});
292257
}
293258

294-
private async filterToOnlyProblemQueries(
295-
queries: string[],
296-
): Promise<string[]> {
297-
const problemQueries: string[] = [];
298-
for (const query of queries) {
299-
const queryMetadata = await this.cliServer.resolveMetadata(query);
300-
if (
301-
queryMetadata.kind === "problem" ||
302-
queryMetadata.kind === "path-problem"
303-
) {
304-
problemQueries.push(query);
305-
} else {
306-
void this.app.logger.log(`Skipping non-problem query ${query}`);
307-
}
308-
}
309-
return problemQueries;
310-
}
311-
312259
private async runVariantAnalysisCommand(queryFiles: Uri[]): Promise<void> {
313260
if (queryFiles.length === 0) {
314261
throw new Error("Please select a .ql file to run as a variant analysis");

extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from "@vscode/webview-ui-toolkit/react";
1515
import type { ModelEditorViewState } from "../../model-editor/shared/view-state";
1616
import type { AccessPathSuggestionOptions } from "../../model-editor/suggestions";
17+
import { getCandidates } from "../../model-editor/shared/auto-model-candidates";
1718

1819
const LibraryContainer = styled.div`
1920
background-color: var(--vscode-peekViewResult-background);
@@ -186,6 +187,17 @@ export const LibraryRow = ({
186187
return methods.some((method) => inProgressMethods.has(method.signature));
187188
}, [methods, inProgressMethods]);
188189

190+
const modelWithAIDisabled = useMemo(() => {
191+
return (
192+
getCandidates(
193+
viewState.mode,
194+
methods,
195+
modeledMethodsMap,
196+
processedByAutoModelMethods,
197+
).length === 0
198+
);
199+
}, [methods, modeledMethodsMap, processedByAutoModelMethods, viewState.mode]);
200+
189201
return (
190202
<LibraryContainer>
191203
<TitleContainer onClick={toggleExpanded} aria-expanded={isExpanded}>
@@ -205,7 +217,11 @@ export const LibraryRow = ({
205217
{hasUnsavedChanges ? <VSCodeTag>UNSAVED</VSCodeTag> : null}
206218
</NameContainer>
207219
{viewState.showLlmButton && !canStopAutoModeling && (
208-
<VSCodeButton appearance="icon" onClick={handleModelWithAI}>
220+
<VSCodeButton
221+
appearance="icon"
222+
disabled={modelWithAIDisabled}
223+
onClick={handleModelWithAI}
224+
>
209225
<Codicon name="lightbulb-autofix" label="Model with AI" />
210226
&nbsp;Model with AI
211227
</VSCodeButton>

0 commit comments

Comments
 (0)