Skip to content

Commit 82cdf03

Browse files
authored
Merge pull request #2521 from github/koesie10/auto-create-model-files
Automatically create different model files per library
2 parents fa23441 + 669f4a6 commit 82cdf03

File tree

14 files changed

+571
-844
lines changed

14 files changed

+571
-844
lines changed

extensions/ql-vscode/src/common/interface-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,7 @@ export interface OpenExtensionPackMessage {
532532

533533
export interface OpenModelFileMessage {
534534
t: "openModelFile";
535+
library: string;
535536
}
536537

537538
export interface SaveModeledMethods {

extensions/ql-vscode/src/view/common/path.ts renamed to extensions/ql-vscode/src/common/path.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,11 @@ export const basename = (path: string): string => {
1919
const index = path.lastIndexOf("\\");
2020
return index === -1 ? path : path.slice(index + 1);
2121
};
22+
23+
// Returns the extension of a path, including the leading dot.
24+
export const extname = (path: string): string => {
25+
const name = basename(path);
26+
27+
const index = name.lastIndexOf(".");
28+
return index === -1 ? "" : name.slice(index);
29+
};

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { ensureDir } from "fs-extra";
88
import { join } from "path";
99
import { App } from "../common/app";
1010
import { withProgress } from "../common/vscode/progress";
11-
import { pickExtensionPackModelFile } from "./extension-pack-picker";
11+
import { pickExtensionPack } from "./extension-pack-picker";
1212
import { showAndLogErrorMessage } from "../common/logging";
1313

1414
const SUPPORTED_LANGUAGES: string[] = ["java", "csharp"];
@@ -78,7 +78,7 @@ export class DataExtensionsEditorModule {
7878
return;
7979
}
8080

81-
const modelFile = await pickExtensionPackModelFile(
81+
const modelFile = await pickExtensionPack(
8282
this.cliServer,
8383
db,
8484
this.app.logger,

extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
window,
77
workspace,
88
} from "vscode";
9+
import { join } from "path";
910
import { RequestError } from "@octokit/request-error";
1011
import {
1112
AbstractWebview,
@@ -21,7 +22,7 @@ import {
2122
showAndLogExceptionWithTelemetry,
2223
showAndLogErrorMessage,
2324
} from "../common/logging";
24-
import { outputFile, pathExists, readFile } from "fs-extra";
25+
import { outputFile, readFile } from "fs-extra";
2526
import { load as loadYaml } from "js-yaml";
2627
import { DatabaseItem, DatabaseManager } from "../databases/local-databases";
2728
import { CodeQLCliServer } from "../codeql-cli/cli";
@@ -34,17 +35,22 @@ import { showResolvableLocation } from "../databases/local-databases/locations";
3435
import { decodeBqrsToExternalApiUsages } from "./bqrs";
3536
import { redactableError } from "../common/errors";
3637
import { readQueryResults, runQuery } from "./external-api-usage-query";
37-
import { createDataExtensionYaml, loadDataExtensionYaml } from "./yaml";
38+
import {
39+
createDataExtensionYamlsPerLibrary,
40+
createFilenameForLibrary,
41+
loadDataExtensionYaml,
42+
} from "./yaml";
3843
import { ExternalApiUsage } from "./external-api-usage";
3944
import { ModeledMethod } from "./modeled-method";
40-
import { ExtensionPackModelFile } from "./shared/extension-pack";
45+
import { ExtensionPack } from "./shared/extension-pack";
4146
import { autoModel, ModelRequest, ModelResponse } from "./auto-model-api";
4247
import {
4348
createAutoModelRequest,
4449
parsePredictedClassifications,
4550
} from "./auto-model";
4651
import { showLlmGeneration } from "../config";
4752
import { getAutoModelUsages } from "./auto-model-usages-query";
53+
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
4854

4955
export class DataExtensionsEditorView extends AbstractWebview<
5056
ToDataExtensionsEditorMessage,
@@ -58,7 +64,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
5864
private readonly queryRunner: QueryRunner,
5965
private readonly queryStorageDir: string,
6066
private readonly databaseItem: DatabaseItem,
61-
private readonly modelFile: ExtensionPackModelFile,
67+
private readonly extensionPack: ExtensionPack,
6268
) {
6369
super(ctx);
6470
}
@@ -95,13 +101,18 @@ export class DataExtensionsEditorView extends AbstractWebview<
95101
case "openExtensionPack":
96102
await this.app.commands.execute(
97103
"revealInExplorer",
98-
Uri.file(this.modelFile.extensionPack.path),
104+
Uri.file(this.extensionPack.path),
99105
);
100106

101107
break;
102108
case "openModelFile":
103109
await window.showTextDocument(
104-
await workspace.openTextDocument(this.modelFile.filename),
110+
await workspace.openTextDocument(
111+
join(
112+
this.extensionPack.path,
113+
createFilenameForLibrary(msg.library),
114+
),
115+
),
105116
);
106117

107118
break;
@@ -147,8 +158,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
147158
await this.postMessage({
148159
t: "setDataExtensionEditorViewState",
149160
viewState: {
150-
extensionPackModelFile: this.modelFile,
151-
modelFileExists: await pathExists(this.modelFile.filename),
161+
extensionPack: this.extensionPack,
152162
showLlmButton: showLlmGeneration(),
153163
},
154164
});
@@ -178,39 +188,55 @@ export class DataExtensionsEditorView extends AbstractWebview<
178188
externalApiUsages: ExternalApiUsage[],
179189
modeledMethods: Record<string, ModeledMethod>,
180190
): Promise<void> {
181-
const yaml = createDataExtensionYaml(
191+
const yamls = createDataExtensionYamlsPerLibrary(
182192
this.databaseItem.language,
183193
externalApiUsages,
184194
modeledMethods,
185195
);
186196

187-
await outputFile(this.modelFile.filename, yaml);
197+
for (const [filename, yaml] of Object.entries(yamls)) {
198+
await outputFile(join(this.extensionPack.path, filename), yaml);
199+
}
188200

189-
void this.app.logger.log(
190-
`Saved data extension YAML to ${this.modelFile.filename}`,
191-
);
201+
void this.app.logger.log(`Saved data extension YAML`);
192202
}
193203

194204
protected async loadExistingModeledMethods(): Promise<void> {
195205
try {
196-
if (!(await pathExists(this.modelFile.filename))) {
197-
return;
206+
const extensions = await this.cliServer.resolveExtensions(
207+
this.extensionPack.path,
208+
getOnDiskWorkspaceFolders(),
209+
);
210+
211+
const modelFiles = new Set<string>();
212+
213+
if (this.extensionPack.path in extensions.data) {
214+
for (const extension of extensions.data[this.extensionPack.path]) {
215+
modelFiles.add(extension.file);
216+
}
198217
}
199218

200-
const yaml = await readFile(this.modelFile.filename, "utf8");
219+
const existingModeledMethods: Record<string, ModeledMethod> = {};
201220

202-
const data = loadYaml(yaml, {
203-
filename: this.modelFile.filename,
204-
});
221+
for (const modelFile of modelFiles) {
222+
const yaml = await readFile(modelFile, "utf8");
205223

206-
const existingModeledMethods = loadDataExtensionYaml(data);
224+
const data = loadYaml(yaml, {
225+
filename: modelFile,
226+
});
207227

208-
if (!existingModeledMethods) {
209-
void showAndLogErrorMessage(
210-
this.app.logger,
211-
`Failed to parse data extension YAML ${this.modelFile.filename}.`,
212-
);
213-
return;
228+
const modeledMethods = loadDataExtensionYaml(data);
229+
if (!modeledMethods) {
230+
void showAndLogErrorMessage(
231+
this.app.logger,
232+
`Failed to parse data extension YAML ${modelFile}.`,
233+
);
234+
continue;
235+
}
236+
237+
for (const [key, value] of Object.entries(modeledMethods)) {
238+
existingModeledMethods[key] = value;
239+
}
214240
}
215241

216242
await this.postMessage({
@@ -220,9 +246,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
220246
} catch (e: unknown) {
221247
void showAndLogErrorMessage(
222248
this.app.logger,
223-
`Unable to read data extension YAML ${
224-
this.modelFile.filename
225-
}: ${getErrorMessage(e)}`,
249+
`Unable to read data extension YAML: ${getErrorMessage(e)}`,
226250
);
227251
}
228252
}

extensions/ql-vscode/src/data-extensions-editor/extension-pack-picker.ts

Lines changed: 3 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
import { join, relative, resolve, sep } from "path";
1+
import { join } from "path";
22
import { outputFile, pathExists, readFile } from "fs-extra";
33
import { dump as dumpYaml, load as loadYaml } from "js-yaml";
4-
import { minimatch } from "minimatch";
54
import { CancellationToken, window } from "vscode";
65
import { CodeQLCliServer, QlpacksInfo } from "../codeql-cli/cli";
76
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
87
import { ProgressCallback } from "../common/vscode/progress";
98
import { DatabaseItem } from "../databases/local-databases";
109
import { getQlPackPath, QLPACK_FILENAMES } from "../common/ql";
1110
import { getErrorMessage } from "../common/helpers-pure";
12-
import { ExtensionPack, ExtensionPackModelFile } from "./shared/extension-pack";
11+
import { ExtensionPack } from "./shared/extension-pack";
1312
import { NotificationLogger, showAndLogErrorMessage } from "../common/logging";
14-
import { containsPath } from "../common/files";
1513
import { disableAutoNameExtensionPack } from "../config";
1614
import {
1715
autoNameExtensionPack,
@@ -27,42 +25,7 @@ import {
2725

2826
const maxStep = 3;
2927

30-
export async function pickExtensionPackModelFile(
31-
cliServer: Pick<CodeQLCliServer, "resolveQlpacks" | "resolveExtensions">,
32-
databaseItem: Pick<DatabaseItem, "name" | "language">,
33-
logger: NotificationLogger,
34-
progress: ProgressCallback,
35-
token: CancellationToken,
36-
): Promise<ExtensionPackModelFile | undefined> {
37-
const extensionPack = await pickExtensionPack(
38-
cliServer,
39-
databaseItem,
40-
logger,
41-
progress,
42-
token,
43-
);
44-
if (!extensionPack) {
45-
return undefined;
46-
}
47-
48-
const modelFile = await pickModelFile(
49-
cliServer,
50-
databaseItem,
51-
extensionPack,
52-
progress,
53-
token,
54-
);
55-
if (!modelFile) {
56-
return;
57-
}
58-
59-
return {
60-
filename: modelFile,
61-
extensionPack,
62-
};
63-
}
64-
65-
async function pickExtensionPack(
28+
export async function pickExtensionPack(
6629
cliServer: Pick<CodeQLCliServer, "resolveQlpacks">,
6730
databaseItem: Pick<DatabaseItem, "name" | "language">,
6831
logger: NotificationLogger,
@@ -190,69 +153,6 @@ async function pickExtensionPack(
190153
return extensionPackOption.extensionPack;
191154
}
192155

193-
async function pickModelFile(
194-
cliServer: Pick<CodeQLCliServer, "resolveExtensions">,
195-
databaseItem: Pick<DatabaseItem, "name">,
196-
extensionPack: ExtensionPack,
197-
progress: ProgressCallback,
198-
token: CancellationToken,
199-
): Promise<string | undefined> {
200-
// Find the existing model files in the extension pack
201-
const additionalPacks = getOnDiskWorkspaceFolders();
202-
const extensions = await cliServer.resolveExtensions(
203-
extensionPack.path,
204-
additionalPacks,
205-
);
206-
207-
const modelFiles = new Set<string>();
208-
209-
if (extensionPack.path in extensions.data) {
210-
for (const extension of extensions.data[extensionPack.path]) {
211-
modelFiles.add(extension.file);
212-
}
213-
}
214-
215-
if (modelFiles.size === 0) {
216-
return pickNewModelFile(databaseItem, extensionPack, token);
217-
}
218-
219-
const fileOptions: Array<{ label: string; file: string | null }> = [];
220-
for (const file of modelFiles) {
221-
fileOptions.push({
222-
label: relative(extensionPack.path, file).replaceAll(sep, "/"),
223-
file,
224-
});
225-
}
226-
fileOptions.push({
227-
label: "Create new model file",
228-
file: null,
229-
});
230-
231-
progress({
232-
message: "Choosing model file...",
233-
step: 3,
234-
maxStep,
235-
});
236-
237-
const fileOption = await window.showQuickPick(
238-
fileOptions,
239-
{
240-
title: "Select model file to use",
241-
},
242-
token,
243-
);
244-
245-
if (!fileOption) {
246-
return undefined;
247-
}
248-
249-
if (fileOption.file) {
250-
return fileOption.file;
251-
}
252-
253-
return pickNewModelFile(databaseItem, extensionPack, token);
254-
}
255-
256156
async function pickNewExtensionPack(
257157
databaseItem: Pick<DatabaseItem, "name" | "language">,
258158
token: CancellationToken,
@@ -428,49 +328,6 @@ async function writeExtensionPack(
428328
return extensionPack;
429329
}
430330

431-
async function pickNewModelFile(
432-
databaseItem: Pick<DatabaseItem, "name">,
433-
extensionPack: ExtensionPack,
434-
token: CancellationToken,
435-
) {
436-
const filename = await window.showInputBox(
437-
{
438-
title: "Enter the name of the new model file",
439-
value: `models/${databaseItem.name.replaceAll("/", ".")}.model.yml`,
440-
validateInput: async (value: string): Promise<string | undefined> => {
441-
if (value === "") {
442-
return "File name must not be empty";
443-
}
444-
445-
const path = resolve(extensionPack.path, value);
446-
447-
if (await pathExists(path)) {
448-
return "File already exists";
449-
}
450-
451-
if (!containsPath(extensionPack.path, path)) {
452-
return "File must be in the extension pack";
453-
}
454-
455-
const matchesPattern = extensionPack.dataExtensions.some((pattern) =>
456-
minimatch(value, pattern, { matchBase: true }),
457-
);
458-
if (!matchesPattern) {
459-
return `File must match one of the patterns in 'dataExtensions' in ${extensionPack.yamlPath}`;
460-
}
461-
462-
return undefined;
463-
},
464-
},
465-
token,
466-
);
467-
if (!filename) {
468-
return undefined;
469-
}
470-
471-
return resolve(extensionPack.path, filename);
472-
}
473-
474331
async function readExtensionPack(path: string): Promise<ExtensionPack> {
475332
const qlpackPath = await getQlPackPath(path);
476333
if (!qlpackPath) {

extensions/ql-vscode/src/data-extensions-editor/shared/extension-pack.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,3 @@ export interface ExtensionPack {
88
extensionTargets: Record<string, string>;
99
dataExtensions: string[];
1010
}
11-
12-
export interface ExtensionPackModelFile {
13-
filename: string;
14-
extensionPack: ExtensionPack;
15-
}

0 commit comments

Comments
 (0)