Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,21 @@ import { extLogger, TeeLogger } from "../common";
import { CoreCompletedQuery, QueryRunner } from "../queryRunner";
import { qlpackOfDatabase } from "../contextual/queryResolver";
import { file } from "tmp-promise";
import { writeFile } from "fs-extra";
import { dump } from "js-yaml";
import { readFile, writeFile } from "fs-extra";
import { dump as dumpYaml, load as loadYaml } from "js-yaml";
import {
getOnDiskWorkspaceFolders,
showAndLogExceptionWithTelemetry,
showAndLogWarningMessage,
} from "../helpers";
import { DatabaseItem } from "../local-databases";
import { CodeQLCliServer } from "../cli";
import { assertNever, asError, getErrorMessage } from "../pure/helpers-pure";
import { asError, assertNever, getErrorMessage } from "../pure/helpers-pure";
import { decodeBqrsToExternalApiUsages } from "./bqrs";
import { redactableError } from "../pure/errors";
import { createDataExtensionYaml, loadDataExtensionYaml } from "./yaml";
import { ExternalApiUsage } from "./external-api-usage";
import { ModeledMethod } from "./modeled-method";

export class DataExtensionsEditorView extends AbstractWebview<
ToDataExtensionsEditorMessage,
Expand Down Expand Up @@ -70,8 +74,11 @@ export class DataExtensionsEditorView extends AbstractWebview<
await this.onWebViewLoaded();

break;
case "applyDataExtensionYaml":
await this.saveYaml(msg.yaml);
case "saveModeledMethods":
await this.saveModeledMethods(
msg.externalApiUsages,
msg.modeledMethods,
);
await this.loadExternalApiUsages();

break;
Expand All @@ -83,20 +90,57 @@ export class DataExtensionsEditorView extends AbstractWebview<
protected async onWebViewLoaded() {
super.onWebViewLoaded();

await this.loadExternalApiUsages();
await Promise.all([
this.loadExternalApiUsages(),
this.loadExistingModeledMethods(),
]);
}

protected async saveYaml(yaml: string): Promise<void> {
protected async saveModeledMethods(
externalApiUsages: ExternalApiUsage[],
modeledMethods: Record<string, ModeledMethod>,
): Promise<void> {
const modelFilename = this.calculateModelFilename();
if (!modelFilename) {
return;
}

const yaml = createDataExtensionYaml(externalApiUsages, modeledMethods);

await writeFile(modelFilename, yaml);

void extLogger.log(`Saved data extension YAML to ${modelFilename}`);
}

protected async loadExistingModeledMethods(): Promise<void> {
const modelFilename = this.calculateModelFilename();
if (!modelFilename) {
return;
}

try {
const yaml = await readFile(modelFilename, "utf8");

const data = loadYaml(yaml, {
filename: modelFilename,
});

const existingModeledMethods = loadDataExtensionYaml(data);

if (!existingModeledMethods) {
void showAndLogWarningMessage("Failed to parse data extension YAML.");
return;
}

await this.postMessage({
t: "setExistingModeledMethods",
existingModeledMethods,
});
} catch (e: unknown) {
void extLogger.log(`Unable to read data extension YAML: ${e}`);
}
}

protected async loadExternalApiUsages(): Promise<void> {
try {
const queryResult = await this.runQuery();
Expand Down Expand Up @@ -165,7 +209,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
},
});
}
await writeFile(suiteFile, dump(suiteYaml), "utf8");
await writeFile(suiteFile, dumpYaml(suiteYaml), "utf8");

const queries = await this.cliServer.resolveQueriesInSuite(
suiteFile,
Expand Down
93 changes: 93 additions & 0 deletions extensions/ql-vscode/src/data-extensions-editor/yaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ type ExternalApiUsageByType = {
type DataExtensionDefinition = {
extensible: string;
generateMethodDefinition: (method: ExternalApiUsageByType) => any[];
readModeledMethod: (row: any[]) => [string, ModeledMethod] | undefined;
};

function readRowToMethod(row: any[]): string {
return `${row[0]}.${row[1]}#${row[3]}${row[4]}`;
}

const definitions: Record<
Exclude<ModeledMethodType, "none">,
DataExtensionDefinition
Expand All @@ -32,6 +37,15 @@ const definitions: Record<
method.modeledMethod.kind,
"manual",
],
readModeledMethod: (row) => [
readRowToMethod(row),
{
type: "source",
input: "",
output: row[6],
kind: row[7],
},
],
},
sink: {
extensible: "sinkModel",
Expand All @@ -50,6 +64,15 @@ const definitions: Record<
method.modeledMethod.kind,
"manual",
],
readModeledMethod: (row) => [
readRowToMethod(row),
{
type: "sink",
input: row[6],
output: "",
kind: row[7],
},
],
},
summary: {
extensible: "summaryModel",
Expand All @@ -69,6 +92,15 @@ const definitions: Record<
method.modeledMethod.kind,
"manual",
],
readModeledMethod: (row) => [
readRowToMethod(row),
{
type: "summary",
input: row[6],
output: row[7],
kind: row[8],
},
],
},
neutral: {
extensible: "neutralModel",
Expand All @@ -82,6 +114,15 @@ const definitions: Record<
method.externalApiUsage.methodParameters,
"manual",
],
readModeledMethod: (row) => [
`${row[0]}.${row[1]}#${row[2]}${row[3]}`,
{
type: "neutral",
input: "",
output: "",
kind: "",
},
],
},
};

Expand Down Expand Up @@ -142,3 +183,55 @@ export function createDataExtensionYaml(
return `extensions:
${extensions.join("\n")}`;
}

export function loadDataExtensionYaml(
data: any,
): Record<string, ModeledMethod> | undefined {
if (typeof data !== "object") {
return undefined;
Comment thread
charisk marked this conversation as resolved.
}

const extensions = data.extensions;
if (!Array.isArray(extensions)) {
return undefined;
}

const modeledMethods: Record<string, ModeledMethod> = {};

for (const extension of extensions) {
const addsTo = extension.addsTo;
if (typeof addsTo !== "object") {
continue;
}

const extensible = addsTo.extensible;
if (typeof extensible !== "string") {
continue;
}

const data = extension.data;
if (!Array.isArray(data)) {
continue;
}

const definition = Object.values(definitions).find(
(definition) => definition.extensible === extensible,
);
if (!definition) {
continue;
}

for (const row of data) {
const result = definition.readModeledMethod(row);
if (!result) {
continue;
}

const [apiInfo, modeledMethod] = result;

modeledMethods[apiInfo] = modeledMethod;
}
}

return modeledMethods;
}
18 changes: 13 additions & 5 deletions extensions/ql-vscode/src/pure/interface-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { RepositoriesFilterSortStateWithIds } from "./variant-analysis-filter-so
import { ErrorLike } from "./errors";
import { DataFlowPaths } from "../variant-analysis/shared/data-flow-paths";
import { ExternalApiUsage } from "../data-extensions-editor/external-api-usage";
import { ModeledMethod } from "../data-extensions-editor/modeled-method";

/**
* This module contains types and code that are shared between
Expand Down Expand Up @@ -492,15 +493,22 @@ export interface ShowProgressMessage {
message: string;
}

export interface ApplyDataExtensionYamlMessage {
t: "applyDataExtensionYaml";
yaml: string;
export interface SetExistingModeledMethods {
t: "setExistingModeledMethods";
existingModeledMethods: Record<string, ModeledMethod>;
}

export interface SaveModeledMethods {
t: "saveModeledMethods";
externalApiUsages: ExternalApiUsage[];
modeledMethods: Record<string, ModeledMethod>;
}

export type ToDataExtensionsEditorMessage =
| SetExternalApiUsagesMessage
| ShowProgressMessage;
| ShowProgressMessage
| SetExistingModeledMethods;

export type FromDataExtensionsEditorMessage =
| ViewLoadedMsg
| ApplyDataExtensionYamlMessage;
| SaveModeledMethods;
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { ModeledMethod } from "../../data-extensions-editor/modeled-method";
import { MethodRow } from "./MethodRow";
import { assertNever } from "../../pure/helpers-pure";
import { vscode } from "../vscode-api";
import { createDataExtensionYaml } from "../../data-extensions-editor/yaml";
import { calculateSupportedPercentage } from "./supported";

export const DataExtensionsEditorContainer = styled.div`
Expand Down Expand Up @@ -57,6 +56,15 @@ export function DataExtensionsEditor(): JSX.Element {
break;
case "showProgress":
setProgress(msg);
break;
case "setExistingModeledMethods":
setModeledMethods((oldModeledMethods) => {
return {
...msg.existingModeledMethods,
...oldModeledMethods,
};
});

break;
default:
assertNever(msg);
Expand Down Expand Up @@ -92,14 +100,10 @@ export function DataExtensionsEditor(): JSX.Element {
);

const onApplyClick = useCallback(() => {
const yamlString = createDataExtensionYaml(
vscode.postMessage({
t: "saveModeledMethods",
externalApiUsages,
modeledMethods,
);

vscode.postMessage({
t: "applyDataExtensionYaml",
yaml: yamlString,
});
}, [externalApiUsages, modeledMethods]);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { createDataExtensionYaml } from "../../../src/data-extensions-editor/yaml";
import {
createDataExtensionYaml,
loadDataExtensionYaml,
} from "../../../src/data-extensions-editor/yaml";

describe("createDataExtensionYaml", () => {
it("creates the correct YAML file", () => {
Expand Down Expand Up @@ -99,3 +102,61 @@ describe("createDataExtensionYaml", () => {
`);
});
});

describe("loadDataExtensionYaml", () => {
it("loads the YAML file", () => {
const data = loadDataExtensionYaml({
extensions: [
{
addsTo: { pack: "codeql/java-all", extensible: "sourceModel" },
data: [],
},
{
addsTo: { pack: "codeql/java-all", extensible: "sinkModel" },
data: [
[
"org.sql2o",
"Connection",
true,
"createQuery",
"(String)",
"",
"Argument[0]",
"sql",
"manual",
],
],
},
{
addsTo: { pack: "codeql/java-all", extensible: "summaryModel" },
data: [],
},
{
addsTo: { pack: "codeql/java-all", extensible: "neutralModel" },
data: [],
},
],
});

expect(data).toEqual({
"org.sql2o.Connection#createQuery(String)": {
input: "Argument[0]",
kind: "sql",
output: "",
type: "sink",
},
});
});

it("returns undefined if given a string", () => {
const data = loadDataExtensionYaml(`extensions:
- addsTo:
pack: codeql/java-all
extensible: sinkModel
data:
- ["org.sql2o","Connection",true,"createQuery","(String)","","Argument[0]","sql","manual"]
`);

expect(data).toBeUndefined();
});
});