Skip to content

Commit 5c12a4b

Browse files
Merge pull request #2605 from github/robertbrignull/data-modeled-methods-tests
Refactor the code for loading/saving modeled methods to disk, and add tests
2 parents 768b95d + 2962306 commit 5c12a4b

6 files changed

Lines changed: 345 additions & 78 deletions

File tree

extensions/ql-vscode/src/codeql-cli/cli.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1806,6 +1806,11 @@ export class CliVersionConstraint {
18061806
"2.10.0",
18071807
);
18081808

1809+
/**
1810+
* CLI version where the `resolve extensions` subcommand exists.
1811+
*/
1812+
public static CLI_VERSION_WITH_RESOLVE_EXTENSIONS = new SemVer("2.10.2");
1813+
18091814
/**
18101815
* CLI version where the `--evaluator-log` and related options to the query server were introduced,
18111816
* on a per-query server basis.
@@ -1882,6 +1887,12 @@ export class CliVersionConstraint {
18821887
);
18831888
}
18841889

1890+
async supportsResolveExtensions() {
1891+
return this.isVersionAtLeast(
1892+
CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_EXTENSIONS,
1893+
);
1894+
}
1895+
18851896
async supportsStructuredEvalLog() {
18861897
return this.isVersionAtLeast(
18871898
CliVersionConstraint.CLI_VERSION_WITH_STRUCTURED_EVAL_LOG,

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ export class DataExtensionsEditorModule {
7878
return;
7979
}
8080

81+
if (
82+
!(await this.cliServer.cliConstraints.supportsResolveExtensions())
83+
) {
84+
void showAndLogErrorMessage(
85+
this.app.logger,
86+
`This feature requires CodeQL CLI version ${CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_EXTENSIONS.format()} or later.`,
87+
);
88+
return;
89+
}
90+
8191
const modelFile = await pickExtensionPack(
8292
this.cliServer,
8393
db,

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

Lines changed: 13 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
ViewColumn,
66
window,
77
} from "vscode";
8-
import { join } from "path";
98
import { RequestError } from "@octokit/request-error";
109
import {
1110
AbstractWebview,
@@ -21,8 +20,6 @@ import {
2120
showAndLogExceptionWithTelemetry,
2221
showAndLogErrorMessage,
2322
} from "../common/logging";
24-
import { outputFile, readFile } from "fs-extra";
25-
import { load as loadYaml } from "js-yaml";
2623
import { DatabaseItem, DatabaseManager } from "../databases/local-databases";
2724
import { CodeQLCliServer } from "../codeql-cli/cli";
2825
import { asError, assertNever, getErrorMessage } from "../common/helpers-pure";
@@ -34,11 +31,6 @@ import { showResolvableLocation } from "../databases/local-databases/locations";
3431
import { decodeBqrsToExternalApiUsages } from "./bqrs";
3532
import { redactableError } from "../common/errors";
3633
import { readQueryResults, runQuery } from "./external-api-usage-query";
37-
import {
38-
createDataExtensionYamlsForApplicationMode,
39-
createDataExtensionYamlsForFrameworkMode,
40-
loadDataExtensionYaml,
41-
} from "./yaml";
4234
import { ExternalApiUsage } from "./external-api-usage";
4335
import { ModeledMethod } from "./modeled-method";
4436
import { ExtensionPack } from "./shared/extension-pack";
@@ -49,8 +41,9 @@ import {
4941
} from "./auto-model";
5042
import { enableFrameworkMode, showLlmGeneration } from "../config";
5143
import { getAutoModelUsages } from "./auto-model-usages-query";
52-
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
5344
import { Mode } from "./shared/mode";
45+
import { loadModeledMethods, saveModeledMethods } from "./modeled-method-fs";
46+
import { join } from "path";
5447

5548
export class DataExtensionsEditorView extends AbstractWebview<
5649
ToDataExtensionsEditorMessage,
@@ -123,9 +116,14 @@ export class DataExtensionsEditorView extends AbstractWebview<
123116

124117
break;
125118
case "saveModeledMethods":
126-
await this.saveModeledMethods(
119+
await saveModeledMethods(
120+
this.extensionPack,
121+
this.databaseItem.name,
122+
this.databaseItem.language,
127123
msg.externalApiUsages,
128124
msg.modeledMethods,
125+
this.mode,
126+
this.app.logger,
129127
);
130128
await Promise.all([this.setViewState(), this.loadExternalApiUsages()]);
131129

@@ -194,79 +192,16 @@ export class DataExtensionsEditorView extends AbstractWebview<
194192
}
195193
}
196194

197-
protected async saveModeledMethods(
198-
externalApiUsages: ExternalApiUsage[],
199-
modeledMethods: Record<string, ModeledMethod>,
200-
): Promise<void> {
201-
let yamls: Record<string, string>;
202-
switch (this.mode) {
203-
case Mode.Application:
204-
yamls = createDataExtensionYamlsForApplicationMode(
205-
this.databaseItem.language,
206-
externalApiUsages,
207-
modeledMethods,
208-
);
209-
break;
210-
case Mode.Framework:
211-
yamls = createDataExtensionYamlsForFrameworkMode(
212-
this.databaseItem.name,
213-
this.databaseItem.language,
214-
externalApiUsages,
215-
modeledMethods,
216-
);
217-
break;
218-
default:
219-
assertNever(this.mode);
220-
}
221-
222-
for (const [filename, yaml] of Object.entries(yamls)) {
223-
await outputFile(join(this.extensionPack.path, filename), yaml);
224-
}
225-
226-
void this.app.logger.log(`Saved data extension YAML`);
227-
}
228-
229195
protected async loadExistingModeledMethods(): Promise<void> {
230196
try {
231-
const extensions = await this.cliServer.resolveExtensions(
232-
this.extensionPack.path,
233-
getOnDiskWorkspaceFolders(),
197+
const modeledMethods = await loadModeledMethods(
198+
this.extensionPack,
199+
this.cliServer,
200+
this.app.logger,
234201
);
235-
236-
const modelFiles = new Set<string>();
237-
238-
if (this.extensionPack.path in extensions.data) {
239-
for (const extension of extensions.data[this.extensionPack.path]) {
240-
modelFiles.add(extension.file);
241-
}
242-
}
243-
244-
const existingModeledMethods: Record<string, ModeledMethod> = {};
245-
246-
for (const modelFile of modelFiles) {
247-
const yaml = await readFile(modelFile, "utf8");
248-
249-
const data = loadYaml(yaml, {
250-
filename: modelFile,
251-
});
252-
253-
const modeledMethods = loadDataExtensionYaml(data);
254-
if (!modeledMethods) {
255-
void showAndLogErrorMessage(
256-
this.app.logger,
257-
`Failed to parse data extension YAML ${modelFile}.`,
258-
);
259-
continue;
260-
}
261-
262-
for (const [key, value] of Object.entries(modeledMethods)) {
263-
existingModeledMethods[key] = value;
264-
}
265-
}
266-
267202
await this.postMessage({
268203
t: "loadModeledMethods",
269-
modeledMethods: existingModeledMethods,
204+
modeledMethods,
270205
});
271206
} catch (e: unknown) {
272207
void showAndLogErrorMessage(
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { outputFile, readFile } from "fs-extra";
2+
import { ExternalApiUsage } from "./external-api-usage";
3+
import { ModeledMethod } from "./modeled-method";
4+
import { Mode } from "./shared/mode";
5+
import { createDataExtensionYamls, loadDataExtensionYaml } from "./yaml";
6+
import { join } from "path";
7+
import { ExtensionPack } from "./shared/extension-pack";
8+
import {
9+
Logger,
10+
NotificationLogger,
11+
showAndLogErrorMessage,
12+
} from "../common/logging";
13+
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
14+
import { load as loadYaml } from "js-yaml";
15+
import { CodeQLCliServer } from "../codeql-cli/cli";
16+
import { pathsEqual } from "../common/files";
17+
18+
export async function saveModeledMethods(
19+
extensionPack: ExtensionPack,
20+
databaseName: string,
21+
language: string,
22+
externalApiUsages: ExternalApiUsage[],
23+
modeledMethods: Record<string, ModeledMethod>,
24+
mode: Mode,
25+
logger: Logger,
26+
): Promise<void> {
27+
const yamls = createDataExtensionYamls(
28+
databaseName,
29+
language,
30+
externalApiUsages,
31+
modeledMethods,
32+
mode,
33+
);
34+
35+
for (const [filename, yaml] of Object.entries(yamls)) {
36+
await outputFile(join(extensionPack.path, filename), yaml);
37+
}
38+
39+
void logger.log(`Saved data extension YAML`);
40+
}
41+
42+
export async function loadModeledMethods(
43+
extensionPack: ExtensionPack,
44+
cliServer: CodeQLCliServer,
45+
logger: NotificationLogger,
46+
): Promise<Record<string, ModeledMethod>> {
47+
const modelFiles = await listModelFiles(extensionPack.path, cliServer);
48+
49+
const existingModeledMethods: Record<string, ModeledMethod> = {};
50+
51+
for (const modelFile of modelFiles) {
52+
const yaml = await readFile(modelFile, "utf8");
53+
54+
const data = loadYaml(yaml, {
55+
filename: modelFile,
56+
});
57+
58+
const modeledMethods = loadDataExtensionYaml(data);
59+
if (!modeledMethods) {
60+
void showAndLogErrorMessage(
61+
logger,
62+
`Failed to parse data extension YAML ${modelFile}.`,
63+
);
64+
continue;
65+
}
66+
67+
for (const [key, value] of Object.entries(modeledMethods)) {
68+
existingModeledMethods[key] = value;
69+
}
70+
}
71+
72+
return existingModeledMethods;
73+
}
74+
75+
export async function listModelFiles(
76+
extensionPackPath: string,
77+
cliServer: CodeQLCliServer,
78+
): Promise<Set<string>> {
79+
const result = await cliServer.resolveExtensions(
80+
extensionPackPath,
81+
getOnDiskWorkspaceFolders(),
82+
);
83+
84+
const modelFiles = new Set<string>();
85+
for (const [path, extensions] of Object.entries(result.data)) {
86+
if (pathsEqual(path, extensionPackPath)) {
87+
for (const extension of extensions) {
88+
modelFiles.add(extension.file);
89+
}
90+
}
91+
}
92+
return modelFiles;
93+
}

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99

1010
import * as dataSchemaJson from "./data-schema.json";
1111
import { sanitizeExtensionPackName } from "./extension-pack-name";
12+
import { Mode } from "./shared/mode";
13+
import { assertNever } from "../common/helpers-pure";
1214

1315
const ajv = new Ajv({ allErrors: true });
1416
const dataSchemaValidate = ajv.compile(dataSchemaJson);
@@ -66,6 +68,32 @@ export function createDataExtensionYaml(
6668
${extensions.join("\n")}`;
6769
}
6870

71+
export function createDataExtensionYamls(
72+
databaseName: string,
73+
language: string,
74+
externalApiUsages: ExternalApiUsage[],
75+
modeledMethods: Record<string, ModeledMethod>,
76+
mode: Mode,
77+
) {
78+
switch (mode) {
79+
case Mode.Application:
80+
return createDataExtensionYamlsForApplicationMode(
81+
language,
82+
externalApiUsages,
83+
modeledMethods,
84+
);
85+
case Mode.Framework:
86+
return createDataExtensionYamlsForFrameworkMode(
87+
databaseName,
88+
language,
89+
externalApiUsages,
90+
modeledMethods,
91+
);
92+
default:
93+
assertNever(mode);
94+
}
95+
}
96+
6997
export function createDataExtensionYamlsForApplicationMode(
7098
language: string,
7199
externalApiUsages: ExternalApiUsage[],

0 commit comments

Comments
 (0)