Use new language definitions for reading/writing

This commit is contained in:
Koen Vlaswinkel
2023-10-26 11:17:37 +02:00
parent df3b94c081
commit 8e721a6670
9 changed files with 118 additions and 55 deletions

View File

@@ -18,6 +18,7 @@ import { Mode } from "./shared/mode";
import { CancellationTokenSource } from "vscode"; import { CancellationTokenSource } from "vscode";
import { ModelingStore } from "./modeling-store"; import { ModelingStore } from "./modeling-store";
import { ModelConfigListener } from "../config"; import { ModelConfigListener } from "../config";
import { QueryLanguage } from "../common/query-language";
/** /**
* The auto-modeler holds state around auto-modeling jobs and allows * The auto-modeler holds state around auto-modeling jobs and allows
@@ -36,6 +37,7 @@ export class AutoModeler {
private readonly modelingStore: ModelingStore, private readonly modelingStore: ModelingStore,
private readonly queryStorageDir: string, private readonly queryStorageDir: string,
private readonly databaseItem: DatabaseItem, private readonly databaseItem: DatabaseItem,
private readonly language: QueryLanguage,
private readonly addModeledMethods: ( private readonly addModeledMethods: (
modeledMethods: Record<string, ModeledMethod[]>, modeledMethods: Record<string, ModeledMethod[]>,
) => Promise<void>, ) => Promise<void>,
@@ -202,7 +204,7 @@ export class AutoModeler {
filename: "auto-model.yml", filename: "auto-model.yml",
}); });
const loadedMethods = loadDataExtensionYaml(models); const loadedMethods = loadDataExtensionYaml(models, this.language);
if (!loadedMethods) { if (!loadedMethods) {
return; return;
} }

View File

@@ -5,7 +5,7 @@ import { QueryRunner } from "../query-server";
import { CodeQLCliServer } from "../codeql-cli/cli"; import { CodeQLCliServer } from "../codeql-cli/cli";
import { showAndLogExceptionWithTelemetry } from "../common/logging"; import { showAndLogExceptionWithTelemetry } from "../common/logging";
import { extLogger } from "../common/logging/vscode"; import { extLogger } from "../common/logging/vscode";
import { extensiblePredicateDefinitions } from "./languages"; import { getModelsAsDataLanguage } from "./languages";
import { ProgressCallback } from "../common/vscode/progress"; import { ProgressCallback } from "../common/vscode/progress";
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders"; import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
import { ModeledMethod, ModeledMethodType } from "./modeled-method"; import { ModeledMethod, ModeledMethodType } from "./modeled-method";
@@ -13,12 +13,14 @@ import { redactableError } from "../common/errors";
import { telemetryListener } from "../common/vscode/telemetry"; import { telemetryListener } from "../common/vscode/telemetry";
import { runQuery } from "../local-queries/run-query"; import { runQuery } from "../local-queries/run-query";
import { resolveQueries } from "../local-queries"; import { resolveQueries } from "../local-queries";
import { QueryLanguage } from "../common/query-language";
type FlowModelOptions = { type FlowModelOptions = {
cliServer: CodeQLCliServer; cliServer: CodeQLCliServer;
queryRunner: QueryRunner; queryRunner: QueryRunner;
queryStorageDir: string; queryStorageDir: string;
databaseItem: DatabaseItem; databaseItem: DatabaseItem;
language: QueryLanguage;
progress: ProgressCallback; progress: ProgressCallback;
token: CancellationToken; token: CancellationToken;
onResults: (results: ModeledMethod[]) => void | Promise<void>; onResults: (results: ModeledMethod[]) => void | Promise<void>;
@@ -104,6 +106,7 @@ async function runSingleFlowQuery(
queryRunner, queryRunner,
queryStorageDir, queryStorageDir,
databaseItem, databaseItem,
language,
progress, progress,
token, token,
}: Omit<FlowModelOptions, "onResults">, }: Omit<FlowModelOptions, "onResults">,
@@ -140,7 +143,12 @@ async function runSingleFlowQuery(
} }
// Interpret the results // Interpret the results
const definition = extensiblePredicateDefinitions[type]; const modelsAsDataLanguage = getModelsAsDataLanguage(language);
if (!modelsAsDataLanguage) {
throw new Error(`No models-as-data definition for ${language}`);
}
const definition = modelsAsDataLanguage[type];
const bqrsPath = completedQuery.outputDir.bqrsPath; const bqrsPath = completedQuery.outputDir.bqrsPath;

View File

@@ -1 +1,3 @@
export * from "./languages";
export * from "./models-as-data";
export * from "./predicates"; export * from "./predicates";

View File

@@ -0,0 +1,14 @@
import { QueryLanguage } from "../../common/query-language";
import { ModelsAsDataLanguage } from "./models-as-data";
import { staticLanguage } from "./static";
const languages: Partial<Record<QueryLanguage, ModelsAsDataLanguage>> = {
[QueryLanguage.CSharp]: staticLanguage,
[QueryLanguage.Java]: staticLanguage,
};
export function getModelsAsDataLanguage(
language: QueryLanguage,
): ModelsAsDataLanguage | undefined {
return languages[language];
}

View File

@@ -87,6 +87,7 @@ export class ModelEditorView extends AbstractWebview<
modelingStore, modelingStore,
queryStorageDir, queryStorageDir,
databaseItem, databaseItem,
language,
async (modeledMethods) => { async (modeledMethods) => {
this.addModeledMethods(modeledMethods); this.addModeledMethods(modeledMethods);
}, },
@@ -223,7 +224,7 @@ export class ModelEditorView extends AbstractWebview<
}); });
await saveModeledMethods( await saveModeledMethods(
this.extensionPack, this.extensionPack,
this.databaseItem.language, this.language,
methods, methods,
modeledMethods, modeledMethods,
mode, mode,
@@ -399,6 +400,7 @@ export class ModelEditorView extends AbstractWebview<
try { try {
const modeledMethods = await loadModeledMethods( const modeledMethods = await loadModeledMethods(
this.extensionPack, this.extensionPack,
this.language,
this.cliServer, this.cliServer,
this.app.logger, this.app.logger,
); );
@@ -463,6 +465,14 @@ export class ModelEditorView extends AbstractWebview<
if (!addedDatabase) { if (!addedDatabase) {
return; return;
} }
if (addedDatabase.language !== this.language) {
void showAndLogErrorMessage(
this.app.logger,
`The selected database is for ${addedDatabase.language}, but the current database is for ${this.language}.`,
);
return;
}
} }
progress({ progress({
@@ -477,6 +487,7 @@ export class ModelEditorView extends AbstractWebview<
queryRunner: this.queryRunner, queryRunner: this.queryRunner,
queryStorageDir: this.queryStorageDir, queryStorageDir: this.queryStorageDir,
databaseItem: addedDatabase ?? this.databaseItem, databaseItem: addedDatabase ?? this.databaseItem,
language: this.language,
onResults: async (modeledMethods) => { onResults: async (modeledMethods) => {
const modeledMethodsByName: Record<string, ModeledMethod[]> = {}; const modeledMethodsByName: Record<string, ModeledMethod[]> = {};

View File

@@ -10,10 +10,11 @@ import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
import { load as loadYaml } from "js-yaml"; import { load as loadYaml } from "js-yaml";
import { CodeQLCliServer } from "../codeql-cli/cli"; import { CodeQLCliServer } from "../codeql-cli/cli";
import { pathsEqual } from "../common/files"; import { pathsEqual } from "../common/files";
import { QueryLanguage } from "../common/query-language";
export async function saveModeledMethods( export async function saveModeledMethods(
extensionPack: ExtensionPack, extensionPack: ExtensionPack,
language: string, language: QueryLanguage,
methods: readonly Method[], methods: readonly Method[],
modeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>, modeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>,
mode: Mode, mode: Mode,
@@ -22,6 +23,7 @@ export async function saveModeledMethods(
): Promise<void> { ): Promise<void> {
const existingModeledMethods = await loadModeledMethodFiles( const existingModeledMethods = await loadModeledMethodFiles(
extensionPack, extensionPack,
language,
cliServer, cliServer,
logger, logger,
); );
@@ -43,6 +45,7 @@ export async function saveModeledMethods(
async function loadModeledMethodFiles( async function loadModeledMethodFiles(
extensionPack: ExtensionPack, extensionPack: ExtensionPack,
language: QueryLanguage,
cliServer: CodeQLCliServer, cliServer: CodeQLCliServer,
logger: NotificationLogger, logger: NotificationLogger,
): Promise<Record<string, Record<string, ModeledMethod[]>>> { ): Promise<Record<string, Record<string, ModeledMethod[]>>> {
@@ -60,7 +63,7 @@ async function loadModeledMethodFiles(
filename: modelFile, filename: modelFile,
}); });
const modeledMethods = loadDataExtensionYaml(data); const modeledMethods = loadDataExtensionYaml(data, language);
if (!modeledMethods) { if (!modeledMethods) {
void showAndLogErrorMessage( void showAndLogErrorMessage(
logger, logger,
@@ -76,6 +79,7 @@ async function loadModeledMethodFiles(
export async function loadModeledMethods( export async function loadModeledMethods(
extensionPack: ExtensionPack, extensionPack: ExtensionPack,
language: QueryLanguage,
cliServer: CodeQLCliServer, cliServer: CodeQLCliServer,
logger: NotificationLogger, logger: NotificationLogger,
): Promise<Record<string, ModeledMethod[]>> { ): Promise<Record<string, ModeledMethod[]>> {
@@ -83,6 +87,7 @@ export async function loadModeledMethods(
const modeledMethodsByFile = await loadModeledMethodFiles( const modeledMethodsByFile = await loadModeledMethodFiles(
extensionPack, extensionPack,
language,
cliServer, cliServer,
logger, logger,
); );

View File

@@ -3,21 +3,22 @@ import Ajv from "ajv";
import { Method } from "./method"; import { Method } from "./method";
import { ModeledMethod, ModeledMethodType } from "./modeled-method"; import { ModeledMethod, ModeledMethodType } from "./modeled-method";
import { import {
ExtensiblePredicateDefinition, getModelsAsDataLanguage,
extensiblePredicateDefinitions, ModelsAsDataLanguageModel,
} from "./languages"; } from "./languages";
import * as modelExtensionFileSchema from "./model-extension-file.schema.json"; import * as modelExtensionFileSchema from "./model-extension-file.schema.json";
import { Mode } from "./shared/mode"; import { Mode } from "./shared/mode";
import { assertNever } from "../common/helpers-pure"; import { assertNever } from "../common/helpers-pure";
import { ModelExtensionFile } from "./model-extension-file"; import { ModelExtensionFile } from "./model-extension-file";
import { QueryLanguage } from "../common/query-language";
const ajv = new Ajv({ allErrors: true, allowUnionTypes: true }); const ajv = new Ajv({ allErrors: true, allowUnionTypes: true });
const modelExtensionFileSchemaValidate = ajv.compile(modelExtensionFileSchema); const modelExtensionFileSchemaValidate = ajv.compile(modelExtensionFileSchema);
function createDataProperty( function createDataProperty(
methods: readonly ModeledMethod[], methods: readonly ModeledMethod[],
definition: ExtensiblePredicateDefinition, definition: ModelsAsDataLanguageModel,
) { ) {
if (methods.length === 0) { if (methods.length === 0) {
return " []"; return " []";
@@ -34,9 +35,14 @@ function createDataProperty(
} }
export function createDataExtensionYaml( export function createDataExtensionYaml(
language: string, language: QueryLanguage,
modeledMethods: readonly ModeledMethod[], modeledMethods: readonly ModeledMethod[],
) { ) {
const modelsAsDataLanguage = getModelsAsDataLanguage(language);
if (!modelsAsDataLanguage) {
throw new Error(`No models as data language for ${language}`);
}
const methodsByType: Record< const methodsByType: Record<
Exclude<ModeledMethodType, "none">, Exclude<ModeledMethodType, "none">,
ModeledMethod[] ModeledMethod[]
@@ -53,7 +59,7 @@ export function createDataExtensionYaml(
} }
} }
const extensions = Object.entries(extensiblePredicateDefinitions).map( const extensions = Object.entries(modelsAsDataLanguage).map(
([type, definition]) => ` - addsTo: ([type, definition]) => ` - addsTo:
pack: codeql/${language}-all pack: codeql/${language}-all
extensible: ${definition.extensiblePredicate} extensible: ${definition.extensiblePredicate}
@@ -69,7 +75,7 @@ ${extensions.join("\n")}`;
} }
export function createDataExtensionYamls( export function createDataExtensionYamls(
language: string, language: QueryLanguage,
methods: readonly Method[], methods: readonly Method[],
newModeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>, newModeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>,
existingModeledMethods: Readonly< existingModeledMethods: Readonly<
@@ -98,7 +104,7 @@ export function createDataExtensionYamls(
} }
function createDataExtensionYamlsByGrouping( function createDataExtensionYamlsByGrouping(
language: string, language: QueryLanguage,
methods: readonly Method[], methods: readonly Method[],
newModeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>, newModeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>,
existingModeledMethods: Readonly< existingModeledMethods: Readonly<
@@ -153,7 +159,7 @@ function createDataExtensionYamlsByGrouping(
} }
export function createDataExtensionYamlsForApplicationMode( export function createDataExtensionYamlsForApplicationMode(
language: string, language: QueryLanguage,
methods: readonly Method[], methods: readonly Method[],
newModeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>, newModeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>,
existingModeledMethods: Readonly< existingModeledMethods: Readonly<
@@ -170,7 +176,7 @@ export function createDataExtensionYamlsForApplicationMode(
} }
export function createDataExtensionYamlsForFrameworkMode( export function createDataExtensionYamlsForFrameworkMode(
language: string, language: QueryLanguage,
methods: readonly Method[], methods: readonly Method[],
newModeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>, newModeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>,
existingModeledMethods: Readonly< existingModeledMethods: Readonly<
@@ -240,11 +246,17 @@ function validateModelExtensionFile(data: unknown): data is ModelExtensionFile {
export function loadDataExtensionYaml( export function loadDataExtensionYaml(
data: unknown, data: unknown,
language: QueryLanguage,
): Record<string, ModeledMethod[]> | undefined { ): Record<string, ModeledMethod[]> | undefined {
if (!validateModelExtensionFile(data)) { if (!validateModelExtensionFile(data)) {
return undefined; return undefined;
} }
const modelsAsDataLanguage = getModelsAsDataLanguage(language);
if (!modelsAsDataLanguage) {
throw new Error(`No models as data language for ${language}`);
}
const extensions = data.extensions; const extensions = data.extensions;
const modeledMethods: Record<string, ModeledMethod[]> = {}; const modeledMethods: Record<string, ModeledMethod[]> = {};
@@ -254,7 +266,7 @@ export function loadDataExtensionYaml(
const extensible = addsTo.extensible; const extensible = addsTo.extensible;
const data = extension.data; const data = extension.data;
const definition = Object.values(extensiblePredicateDefinitions).find( const definition = Object.values(modelsAsDataLanguage).find(
(definition) => definition.extensiblePredicate === extensible, (definition) => definition.extensiblePredicate === extensible,
); );
if (!definition) { if (!definition) {

View File

@@ -7,10 +7,11 @@ import {
loadDataExtensionYaml, loadDataExtensionYaml,
} from "../../../src/model-editor/yaml"; } from "../../../src/model-editor/yaml";
import { CallClassification } from "../../../src/model-editor/method"; import { CallClassification } from "../../../src/model-editor/method";
import { QueryLanguage } from "../../../src/common/query-language";
describe("createDataExtensionYaml", () => { describe("createDataExtensionYaml", () => {
it("creates the correct YAML file", () => { it("creates the correct YAML file", () => {
const yaml = createDataExtensionYaml("java", [ const yaml = createDataExtensionYaml(QueryLanguage.Java, [
{ {
type: "sink", type: "sink",
input: "Argument[0]", input: "Argument[0]",
@@ -50,7 +51,7 @@ describe("createDataExtensionYaml", () => {
}); });
it("includes the correct language", () => { it("includes the correct language", () => {
const yaml = createDataExtensionYaml("csharp", []); const yaml = createDataExtensionYaml(QueryLanguage.CSharp, []);
expect(yaml).toEqual(`extensions: expect(yaml).toEqual(`extensions:
- addsTo: - addsTo:
@@ -79,7 +80,7 @@ describe("createDataExtensionYaml", () => {
describe("createDataExtensionYamlsForApplicationMode", () => { describe("createDataExtensionYamlsForApplicationMode", () => {
it("creates the correct YAML files when there are no existing modeled methods", () => { it("creates the correct YAML files when there are no existing modeled methods", () => {
const yaml = createDataExtensionYamlsForApplicationMode( const yaml = createDataExtensionYamlsForApplicationMode(
"java", QueryLanguage.Java,
[ [
{ {
library: "sql2o", library: "sql2o",
@@ -323,7 +324,7 @@ describe("createDataExtensionYamlsForApplicationMode", () => {
it("creates the correct YAML files when there are existing modeled methods", () => { it("creates the correct YAML files when there are existing modeled methods", () => {
const yaml = createDataExtensionYamlsForApplicationMode( const yaml = createDataExtensionYamlsForApplicationMode(
"java", QueryLanguage.Java,
[ [
{ {
library: "sql2o", library: "sql2o",
@@ -618,7 +619,7 @@ describe("createDataExtensionYamlsForApplicationMode", () => {
describe("createDataExtensionYamlsForFrameworkMode", () => { describe("createDataExtensionYamlsForFrameworkMode", () => {
it("creates the correct YAML files when there are no existing modeled methods", () => { it("creates the correct YAML files when there are no existing modeled methods", () => {
const yaml = createDataExtensionYamlsForFrameworkMode( const yaml = createDataExtensionYamlsForFrameworkMode(
"java", QueryLanguage.Java,
[ [
{ {
library: "sql2o", library: "sql2o",
@@ -774,7 +775,7 @@ describe("createDataExtensionYamlsForFrameworkMode", () => {
it("creates the correct YAML files when there are existing modeled methods", () => { it("creates the correct YAML files when there are existing modeled methods", () => {
const yaml = createDataExtensionYamlsForFrameworkMode( const yaml = createDataExtensionYamlsForFrameworkMode(
"java", QueryLanguage.Java,
[ [
{ {
library: "sql2o", library: "sql2o",
@@ -980,38 +981,41 @@ describe("createDataExtensionYamlsForFrameworkMode", () => {
describe("loadDataExtensionYaml", () => { describe("loadDataExtensionYaml", () => {
it("loads the YAML file", () => { it("loads the YAML file", () => {
const data = loadDataExtensionYaml({ const data = loadDataExtensionYaml(
extensions: [ {
{ extensions: [
addsTo: { pack: "codeql/java-all", extensible: "sourceModel" }, {
data: [], addsTo: { pack: "codeql/java-all", extensible: "sourceModel" },
}, data: [],
{ },
addsTo: { pack: "codeql/java-all", extensible: "sinkModel" }, {
data: [ addsTo: { pack: "codeql/java-all", extensible: "sinkModel" },
[ data: [
"org.sql2o", [
"Connection", "org.sql2o",
true, "Connection",
"createQuery", true,
"(String)", "createQuery",
"", "(String)",
"Argument[0]", "",
"sql", "Argument[0]",
"manual", "sql",
"manual",
],
], ],
], },
}, {
{ addsTo: { pack: "codeql/java-all", extensible: "summaryModel" },
addsTo: { pack: "codeql/java-all", extensible: "summaryModel" }, data: [],
data: [], },
}, {
{ addsTo: { pack: "codeql/java-all", extensible: "neutralModel" },
addsTo: { pack: "codeql/java-all", extensible: "neutralModel" }, data: [],
data: [], },
}, ],
], },
}); QueryLanguage.Java,
);
expect(data).toEqual({ expect(data).toEqual({
"org.sql2o.Connection#createQuery(String)": [ "org.sql2o.Connection#createQuery(String)": [
@@ -1033,13 +1037,16 @@ describe("loadDataExtensionYaml", () => {
it("returns undefined if given a string", () => { it("returns undefined if given a string", () => {
expect(() => expect(() =>
loadDataExtensionYaml(`extensions: loadDataExtensionYaml(
`extensions:
- addsTo: - addsTo:
pack: codeql/java-all pack: codeql/java-all
extensible: sinkModel extensible: sinkModel
data: data:
- ["org.sql2o","Connection",true,"createQuery","(String)","","Argument[0]","sql","manual"] - ["org.sql2o","Connection",true,"createQuery","(String)","","Argument[0]","sql","manual"]
`), `,
QueryLanguage.Java,
),
).toThrow("Invalid data extension YAML: must be object"); ).toThrow("Invalid data extension YAML: must be object");
}); });
}); });

View File

@@ -11,6 +11,7 @@ import { ExtensionPack } from "../../../../src/model-editor/shared/extension-pac
import { join } from "path"; import { join } from "path";
import { extLogger } from "../../../../src/common/logging/vscode"; import { extLogger } from "../../../../src/common/logging/vscode";
import { homedir } from "os"; import { homedir } from "os";
import { QueryLanguage } from "../../../../src/common/query-language";
const dummyExtensionPackContents = ` const dummyExtensionPackContents = `
name: dummy/pack name: dummy/pack
@@ -192,6 +193,7 @@ describe("modeled-method-fs", () => {
const modeledMethods = await loadModeledMethods( const modeledMethods = await loadModeledMethods(
makeExtensionPack(extensionPackPath), makeExtensionPack(extensionPackPath),
QueryLanguage.Java,
cli, cli,
extLogger, extLogger,
); );