Merge remote-tracking branch 'origin/main' into koesie10/generate-model-unify
This commit is contained in:
6
extensions/ql-vscode/src/common/mutable.ts
Normal file
6
extensions/ql-vscode/src/common/mutable.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Remove all readonly modifiers from a type.
|
||||
*/
|
||||
export type Mutable<T> = {
|
||||
-readonly [P in keyof T]: T[P];
|
||||
};
|
||||
@@ -248,8 +248,10 @@ export class DatabaseManager extends DisposableObject {
|
||||
const firstWorkspaceFolder = getFirstWorkspaceFolder();
|
||||
const folderName = `codeql-custom-queries-${databaseItem.language}`;
|
||||
|
||||
const qlpackStoragePath = join(firstWorkspaceFolder, folderName);
|
||||
|
||||
if (
|
||||
existsSync(join(firstWorkspaceFolder, folderName)) ||
|
||||
existsSync(qlpackStoragePath) ||
|
||||
isFolderAlreadyInWorkspace(folderName)
|
||||
) {
|
||||
return;
|
||||
@@ -276,7 +278,8 @@ export class DatabaseManager extends DisposableObject {
|
||||
const qlPackGenerator = new QlPackGenerator(
|
||||
databaseItem.language,
|
||||
this.cli,
|
||||
join(firstWorkspaceFolder, folderName),
|
||||
qlpackStoragePath,
|
||||
qlpackStoragePath,
|
||||
);
|
||||
await qlPackGenerator.generate();
|
||||
} catch (e: unknown) {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { mkdir, writeFile } from "fs-extra";
|
||||
import { dump } from "js-yaml";
|
||||
import { join } from "path";
|
||||
import { dirname, join } from "path";
|
||||
import { Uri } from "vscode";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { QueryLanguage } from "../common/query-language";
|
||||
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||
import { basename } from "../common/path";
|
||||
|
||||
export class QlPackGenerator {
|
||||
private readonly qlpackName: string;
|
||||
private qlpackName: string | undefined;
|
||||
private readonly qlpackVersion: string;
|
||||
private readonly header: string;
|
||||
private readonly qlpackFileName: string;
|
||||
@@ -16,8 +18,9 @@ export class QlPackGenerator {
|
||||
private readonly queryLanguage: QueryLanguage,
|
||||
private readonly cliServer: CodeQLCliServer,
|
||||
private readonly storagePath: string,
|
||||
private readonly queryStoragePath: string,
|
||||
private readonly includeFolderNameInQlpackName: boolean = false,
|
||||
) {
|
||||
this.qlpackName = `getting-started/codeql-extra-queries-${this.queryLanguage}`;
|
||||
this.qlpackVersion = "1.0.0";
|
||||
this.header = "# This is an automatically generated file.\n\n";
|
||||
|
||||
@@ -26,6 +29,8 @@ export class QlPackGenerator {
|
||||
}
|
||||
|
||||
public async generate() {
|
||||
this.qlpackName = await this.determineQlpackName();
|
||||
|
||||
// create QL pack folder and add to workspace
|
||||
await this.createWorkspaceFolder();
|
||||
|
||||
@@ -39,6 +44,37 @@ export class QlPackGenerator {
|
||||
await this.createCodeqlPackLockYaml();
|
||||
}
|
||||
|
||||
private async determineQlpackName(): Promise<string> {
|
||||
let qlpackBaseName = `getting-started/codeql-extra-queries-${this.queryLanguage}`;
|
||||
if (this.includeFolderNameInQlpackName) {
|
||||
const folderBasename = basename(dirname(this.folderUri.fsPath));
|
||||
if (
|
||||
folderBasename.includes("codeql") ||
|
||||
folderBasename.includes("queries")
|
||||
) {
|
||||
// If the user has already included "codeql" or "queries" in the folder name, don't include it twice
|
||||
qlpackBaseName = `getting-started/${folderBasename}-${this.queryLanguage}`;
|
||||
} else {
|
||||
qlpackBaseName = `getting-started/codeql-extra-queries-${folderBasename}-${this.queryLanguage}`;
|
||||
}
|
||||
}
|
||||
|
||||
const existingQlPacks = await this.cliServer.resolveQlpacks(
|
||||
getOnDiskWorkspaceFolders(),
|
||||
);
|
||||
const existingQlPackNames = Object.keys(existingQlPacks);
|
||||
|
||||
let qlpackName = qlpackBaseName;
|
||||
let i = 0;
|
||||
while (existingQlPackNames.includes(qlpackName)) {
|
||||
i++;
|
||||
|
||||
qlpackName = `${qlpackBaseName}-${i}`;
|
||||
}
|
||||
|
||||
return qlpackName;
|
||||
}
|
||||
|
||||
private async createWorkspaceFolder() {
|
||||
await mkdir(this.folderUri.fsPath);
|
||||
}
|
||||
@@ -56,7 +92,7 @@ export class QlPackGenerator {
|
||||
}
|
||||
|
||||
public async createExampleQlFile(fileName = "example.ql") {
|
||||
const exampleQlFilePath = join(this.folderUri.fsPath, fileName);
|
||||
const exampleQlFilePath = join(this.queryStoragePath, fileName);
|
||||
|
||||
const exampleQl = `
|
||||
/**
|
||||
|
||||
@@ -34,7 +34,7 @@ import { showInformationMessageWithAction } from "../common/vscode/dialog";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { App } from "../common/app";
|
||||
import { QueryTreeViewItem } from "../queries-panel/query-tree-view-item";
|
||||
import { containsPath } from "../common/files";
|
||||
import { containsPath, pathsEqual } from "../common/files";
|
||||
import { getQlPackPath } from "../common/ql";
|
||||
import { load } from "js-yaml";
|
||||
import { QlPackFile } from "../packaging/qlpack-file";
|
||||
@@ -284,13 +284,6 @@ export class SkeletonQueryWizard {
|
||||
}
|
||||
|
||||
private async createQlPack() {
|
||||
if (this.qlPackStoragePath === undefined) {
|
||||
throw new Error("Query pack storage path is undefined");
|
||||
}
|
||||
if (this.language === undefined) {
|
||||
throw new Error("Language is undefined");
|
||||
}
|
||||
|
||||
this.progress({
|
||||
message: "Creating skeleton QL pack around query",
|
||||
step: 2,
|
||||
@@ -298,11 +291,7 @@ export class SkeletonQueryWizard {
|
||||
});
|
||||
|
||||
try {
|
||||
const qlPackGenerator = new QlPackGenerator(
|
||||
this.language,
|
||||
this.cliServer,
|
||||
this.qlPackStoragePath,
|
||||
);
|
||||
const qlPackGenerator = this.createQlPackGenerator();
|
||||
|
||||
await qlPackGenerator.generate();
|
||||
} catch (e: unknown) {
|
||||
@@ -313,13 +302,6 @@ export class SkeletonQueryWizard {
|
||||
}
|
||||
|
||||
private async createExampleFile() {
|
||||
if (this.qlPackStoragePath === undefined) {
|
||||
throw new Error("Folder name is undefined");
|
||||
}
|
||||
if (this.language === undefined) {
|
||||
throw new Error("Language is undefined");
|
||||
}
|
||||
|
||||
this.progress({
|
||||
message:
|
||||
"Skeleton query pack already exists. Creating additional query example file.",
|
||||
@@ -328,11 +310,7 @@ export class SkeletonQueryWizard {
|
||||
});
|
||||
|
||||
try {
|
||||
const qlPackGenerator = new QlPackGenerator(
|
||||
this.language,
|
||||
this.cliServer,
|
||||
this.qlPackStoragePath,
|
||||
);
|
||||
const qlPackGenerator = this.createQlPackGenerator();
|
||||
|
||||
this.fileName = await this.determineNextFileName();
|
||||
await qlPackGenerator.createExampleQlFile(this.fileName);
|
||||
@@ -475,6 +453,33 @@ export class SkeletonQueryWizard {
|
||||
return `[${this.fileName}](command:vscode.open?${queryString})`;
|
||||
}
|
||||
|
||||
private createQlPackGenerator() {
|
||||
if (this.qlPackStoragePath === undefined) {
|
||||
throw new Error("QL pack storage path is undefined");
|
||||
}
|
||||
if (this.queryStoragePath === undefined) {
|
||||
throw new Error("Query storage path is undefined");
|
||||
}
|
||||
if (this.language === undefined) {
|
||||
throw new Error("Language is undefined");
|
||||
}
|
||||
|
||||
const parentFolder = dirname(this.qlPackStoragePath);
|
||||
|
||||
// Only include the folder name in the qlpack name if the qlpack is not in the root of the workspace.
|
||||
const includeFolderNameInQlpackName = !getOnDiskWorkspaceFolders().some(
|
||||
(workspaceFolder) => pathsEqual(workspaceFolder, parentFolder),
|
||||
);
|
||||
|
||||
return new QlPackGenerator(
|
||||
this.language,
|
||||
this.cliServer,
|
||||
this.qlPackStoragePath,
|
||||
this.queryStoragePath,
|
||||
includeFolderNameInQlpackName,
|
||||
);
|
||||
}
|
||||
|
||||
public static async findDatabaseItemByNwo(
|
||||
language: string,
|
||||
databaseNwo: string,
|
||||
|
||||
@@ -218,8 +218,6 @@ export class AutoModeler {
|
||||
{
|
||||
type: "neutral",
|
||||
kind: "sink",
|
||||
input: "",
|
||||
output: "",
|
||||
provenance: "ai-generated",
|
||||
signature: candidate.signature,
|
||||
packageName: candidate.packageName,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { QueryLanguage } from "../../common/query-language";
|
||||
import { ModelsAsDataLanguage } from "./models-as-data";
|
||||
import {
|
||||
ModelsAsDataLanguage,
|
||||
ModelsAsDataLanguagePredicates,
|
||||
} from "./models-as-data";
|
||||
import { ruby } from "./ruby";
|
||||
import { staticLanguage } from "./static";
|
||||
|
||||
@@ -18,3 +21,16 @@ export function getModelsAsDataLanguage(
|
||||
}
|
||||
return definition;
|
||||
}
|
||||
|
||||
export function getModelsAsDataLanguageModel<
|
||||
T extends keyof ModelsAsDataLanguagePredicates,
|
||||
>(
|
||||
language: QueryLanguage,
|
||||
model: T,
|
||||
): NonNullable<ModelsAsDataLanguagePredicates[T]> {
|
||||
const definition = getModelsAsDataLanguage(language).predicates[model];
|
||||
if (!definition) {
|
||||
throw new Error(`No models-as-data predicate for ${model}`);
|
||||
}
|
||||
return definition;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
import { MethodDefinition } from "../method";
|
||||
import { ModeledMethod, ModeledMethodType } from "../modeled-method";
|
||||
import {
|
||||
ModeledMethod,
|
||||
NeutralModeledMethod,
|
||||
SinkModeledMethod,
|
||||
SourceModeledMethod,
|
||||
SummaryModeledMethod,
|
||||
} from "../modeled-method";
|
||||
import { DataTuple } from "../model-extension-file";
|
||||
import { Mode } from "../shared/mode";
|
||||
import type { QueryConstraints } from "../../local-queries/query-constraints";
|
||||
import { DecodedBqrs } from "../../common/bqrs-cli-types";
|
||||
import { BaseLogger } from "../../common/logging";
|
||||
|
||||
type GenerateMethodDefinition = (method: ModeledMethod) => DataTuple[];
|
||||
type GenerateMethodDefinition<T> = (method: T) => DataTuple[];
|
||||
type ReadModeledMethod = (row: DataTuple[]) => ModeledMethod;
|
||||
|
||||
export type ModelsAsDataLanguageModelType = Exclude<ModeledMethodType, "none">;
|
||||
|
||||
export type ModelsAsDataLanguagePredicate = {
|
||||
export type ModelsAsDataLanguagePredicate<T> = {
|
||||
extensiblePredicate: string;
|
||||
supportedKinds: string[];
|
||||
generateMethodDefinition: GenerateMethodDefinition;
|
||||
generateMethodDefinition: GenerateMethodDefinition<T>;
|
||||
readModeledMethod: ReadModeledMethod;
|
||||
};
|
||||
|
||||
@@ -34,10 +38,12 @@ type ModelsAsDataLanguageModelGeneration = {
|
||||
) => ModeledMethod[];
|
||||
};
|
||||
|
||||
export type ModelsAsDataLanguagePredicates = Record<
|
||||
ModelsAsDataLanguageModelType,
|
||||
ModelsAsDataLanguagePredicate
|
||||
>;
|
||||
export type ModelsAsDataLanguagePredicates = {
|
||||
source?: ModelsAsDataLanguagePredicate<SourceModeledMethod>;
|
||||
sink?: ModelsAsDataLanguagePredicate<SinkModeledMethod>;
|
||||
summary?: ModelsAsDataLanguagePredicate<SummaryModeledMethod>;
|
||||
neutral?: ModelsAsDataLanguagePredicate<NeutralModeledMethod>;
|
||||
};
|
||||
|
||||
export type ModelsAsDataLanguage = {
|
||||
/**
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { BaseLogger } from "../../../common/logging";
|
||||
import {
|
||||
ModelsAsDataLanguage,
|
||||
ModelsAsDataLanguageModelType,
|
||||
ModelsAsDataLanguagePredicates,
|
||||
} from "../models-as-data";
|
||||
import { DecodedBqrs } from "../../../common/bqrs-cli-types";
|
||||
import { ModeledMethod } from "../../modeled-method";
|
||||
import { basename } from "../../../common/path";
|
||||
|
||||
const queriesToModel: Record<string, ModelsAsDataLanguageModelType> = {
|
||||
const queriesToModel: Record<string, keyof ModelsAsDataLanguagePredicates> = {
|
||||
"CaptureSummaryModels.ql": "summary",
|
||||
"CaptureSinkModels.ql": "sink",
|
||||
"CaptureSourceModels.ql": "source",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ModelsAsDataLanguage } from "../models-as-data";
|
||||
import { ModeledMethodType, Provenance } from "../../modeled-method";
|
||||
import { Provenance } from "../../modeled-method";
|
||||
import { DataTuple } from "../../model-extension-file";
|
||||
import { sharedExtensiblePredicates, sharedKinds } from "../shared";
|
||||
import { filterFlowModelQueries, parseFlowModelResults } from "./generate";
|
||||
@@ -35,7 +35,7 @@ export const staticLanguage: ModelsAsDataLanguage = {
|
||||
method.provenance,
|
||||
],
|
||||
readModeledMethod: (row) => ({
|
||||
type: "source" as ModeledMethodType,
|
||||
type: "source",
|
||||
input: "",
|
||||
output: row[6] as string,
|
||||
kind: row[7] as string,
|
||||
|
||||
@@ -137,6 +137,9 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
|
||||
}
|
||||
case "revealInModelEditor":
|
||||
await this.revealInModelEditor(msg.method);
|
||||
void telemetryListener?.sendUIInteraction(
|
||||
"method-modeling-reveal-in-model-editor",
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { ModeledMethod, SinkModeledMethod } from "./modeled-method";
|
||||
import { MethodSignature } from "./method";
|
||||
import { assertNever } from "../common/helpers-pure";
|
||||
|
||||
export function createEmptyModeledMethod(
|
||||
type: ModeledMethod["type"],
|
||||
methodSignature: MethodSignature,
|
||||
) {
|
||||
const canonicalMethodSignature: MethodSignature = {
|
||||
packageName: methodSignature.packageName,
|
||||
typeName: methodSignature.typeName,
|
||||
methodName: methodSignature.methodName,
|
||||
methodParameters: methodSignature.methodParameters,
|
||||
signature: methodSignature.signature,
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case "none":
|
||||
return createEmptyNoneModeledMethod(canonicalMethodSignature);
|
||||
case "source":
|
||||
return createEmptySourceModeledMethod(canonicalMethodSignature);
|
||||
case "sink":
|
||||
return createEmptySinkModeledMethod(canonicalMethodSignature);
|
||||
case "summary":
|
||||
return createEmptySummaryModeledMethod(canonicalMethodSignature);
|
||||
case "neutral":
|
||||
return createEmptyNeutralModeledMethod(canonicalMethodSignature);
|
||||
default:
|
||||
assertNever(type);
|
||||
}
|
||||
}
|
||||
|
||||
function createEmptyNoneModeledMethod(
|
||||
methodSignature: MethodSignature,
|
||||
): ModeledMethod {
|
||||
return {
|
||||
...methodSignature,
|
||||
type: "none",
|
||||
};
|
||||
}
|
||||
|
||||
function createEmptySourceModeledMethod(
|
||||
methodSignature: MethodSignature,
|
||||
): ModeledMethod {
|
||||
return {
|
||||
...methodSignature,
|
||||
type: "source",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "manual",
|
||||
};
|
||||
}
|
||||
|
||||
function createEmptySinkModeledMethod(
|
||||
methodSignature: MethodSignature,
|
||||
): SinkModeledMethod {
|
||||
return {
|
||||
...methodSignature,
|
||||
type: "sink",
|
||||
input: "",
|
||||
kind: "",
|
||||
provenance: "manual",
|
||||
};
|
||||
}
|
||||
|
||||
function createEmptySummaryModeledMethod(
|
||||
methodSignature: MethodSignature,
|
||||
): ModeledMethod {
|
||||
return {
|
||||
...methodSignature,
|
||||
type: "summary",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "manual",
|
||||
};
|
||||
}
|
||||
|
||||
function createEmptyNeutralModeledMethod(
|
||||
methodSignature: MethodSignature,
|
||||
): ModeledMethod {
|
||||
return {
|
||||
...methodSignature,
|
||||
type: "neutral",
|
||||
kind: "",
|
||||
provenance: "manual",
|
||||
};
|
||||
}
|
||||
@@ -19,12 +19,85 @@ export type Provenance =
|
||||
// Entered by the user in the editor manually
|
||||
| "manual";
|
||||
|
||||
export interface ModeledMethod extends MethodSignature {
|
||||
readonly type: ModeledMethodType;
|
||||
export interface NoneModeledMethod extends MethodSignature {
|
||||
readonly type: "none";
|
||||
}
|
||||
|
||||
export interface SourceModeledMethod extends MethodSignature {
|
||||
readonly type: "source";
|
||||
readonly output: string;
|
||||
readonly kind: ModeledMethodKind;
|
||||
readonly provenance: Provenance;
|
||||
}
|
||||
|
||||
export interface SinkModeledMethod extends MethodSignature {
|
||||
readonly type: "sink";
|
||||
readonly input: string;
|
||||
readonly kind: ModeledMethodKind;
|
||||
readonly provenance: Provenance;
|
||||
}
|
||||
|
||||
export interface SummaryModeledMethod extends MethodSignature {
|
||||
readonly type: "summary";
|
||||
readonly input: string;
|
||||
readonly output: string;
|
||||
readonly kind: ModeledMethodKind;
|
||||
readonly provenance: Provenance;
|
||||
}
|
||||
|
||||
export interface NeutralModeledMethod extends MethodSignature {
|
||||
readonly type: "neutral";
|
||||
readonly kind: ModeledMethodKind;
|
||||
readonly provenance: Provenance;
|
||||
}
|
||||
|
||||
export type ModeledMethod =
|
||||
| NoneModeledMethod
|
||||
| SourceModeledMethod
|
||||
| SinkModeledMethod
|
||||
| SummaryModeledMethod
|
||||
| NeutralModeledMethod;
|
||||
|
||||
export type ModeledMethodKind = string;
|
||||
|
||||
export function modeledMethodSupportsKind(
|
||||
modeledMethod: ModeledMethod,
|
||||
): modeledMethod is
|
||||
| SourceModeledMethod
|
||||
| SinkModeledMethod
|
||||
| SummaryModeledMethod
|
||||
| NeutralModeledMethod {
|
||||
return (
|
||||
modeledMethod.type === "source" ||
|
||||
modeledMethod.type === "sink" ||
|
||||
modeledMethod.type === "summary" ||
|
||||
modeledMethod.type === "neutral"
|
||||
);
|
||||
}
|
||||
|
||||
export function modeledMethodSupportsInput(
|
||||
modeledMethod: ModeledMethod,
|
||||
): modeledMethod is SinkModeledMethod | SummaryModeledMethod {
|
||||
return modeledMethod.type === "sink" || modeledMethod.type === "summary";
|
||||
}
|
||||
|
||||
export function modeledMethodSupportsOutput(
|
||||
modeledMethod: ModeledMethod,
|
||||
): modeledMethod is SourceModeledMethod | SummaryModeledMethod {
|
||||
return modeledMethod.type === "source" || modeledMethod.type === "summary";
|
||||
}
|
||||
|
||||
export function modeledMethodSupportsProvenance(
|
||||
modeledMethod: ModeledMethod,
|
||||
): modeledMethod is
|
||||
| SourceModeledMethod
|
||||
| SinkModeledMethod
|
||||
| SummaryModeledMethod
|
||||
| NeutralModeledMethod {
|
||||
return (
|
||||
modeledMethod.type === "source" ||
|
||||
modeledMethod.type === "sink" ||
|
||||
modeledMethod.type === "summary" ||
|
||||
modeledMethod.type === "neutral"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ModeledMethod } from "../modeled-method";
|
||||
import { ModeledMethod, NeutralModeledMethod } from "../modeled-method";
|
||||
import { MethodSignature } from "../method";
|
||||
import { assertNever } from "../../common/helpers-pure";
|
||||
|
||||
@@ -37,16 +37,11 @@ function canonicalizeModeledMethod(
|
||||
return {
|
||||
...methodSignature,
|
||||
type: "none",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "manual",
|
||||
};
|
||||
case "source":
|
||||
return {
|
||||
...methodSignature,
|
||||
type: "source",
|
||||
input: "",
|
||||
output: modeledMethod.output,
|
||||
kind: modeledMethod.kind,
|
||||
provenance: "manual",
|
||||
@@ -56,7 +51,6 @@ function canonicalizeModeledMethod(
|
||||
...methodSignature,
|
||||
type: "sink",
|
||||
input: modeledMethod.input,
|
||||
output: "",
|
||||
kind: modeledMethod.kind,
|
||||
provenance: "manual",
|
||||
};
|
||||
@@ -73,13 +67,11 @@ function canonicalizeModeledMethod(
|
||||
return {
|
||||
...methodSignature,
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: modeledMethod.kind,
|
||||
provenance: "manual",
|
||||
};
|
||||
default:
|
||||
assertNever(modeledMethod.type);
|
||||
assertNever(modeledMethod);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +110,8 @@ export function validateModeledMethods(
|
||||
}
|
||||
|
||||
const neutralModeledMethods = consideredModeledMethods.filter(
|
||||
(modeledMethod) => modeledMethod.type === "neutral",
|
||||
(modeledMethod): modeledMethod is NeutralModeledMethod =>
|
||||
modeledMethod.type === "neutral",
|
||||
);
|
||||
|
||||
const neutralModeledMethodsByKind = new Map<string, ModeledMethod[]>();
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import Ajv from "ajv";
|
||||
|
||||
import { Method } from "./method";
|
||||
import { ModeledMethod, ModeledMethodType } from "./modeled-method";
|
||||
import {
|
||||
ModeledMethod,
|
||||
NeutralModeledMethod,
|
||||
SinkModeledMethod,
|
||||
SourceModeledMethod,
|
||||
SummaryModeledMethod,
|
||||
} from "./modeled-method";
|
||||
import {
|
||||
getModelsAsDataLanguage,
|
||||
ModelsAsDataLanguagePredicate,
|
||||
ModelsAsDataLanguagePredicates,
|
||||
} from "./languages";
|
||||
|
||||
import * as modelExtensionFileSchema from "./model-extension-file.schema.json";
|
||||
@@ -16,9 +23,9 @@ import { QueryLanguage } from "../common/query-language";
|
||||
const ajv = new Ajv({ allErrors: true, allowUnionTypes: true });
|
||||
const modelExtensionFileSchemaValidate = ajv.compile(modelExtensionFileSchema);
|
||||
|
||||
function createDataProperty(
|
||||
methods: readonly ModeledMethod[],
|
||||
definition: ModelsAsDataLanguagePredicate,
|
||||
function createDataProperty<T>(
|
||||
methods: readonly T[],
|
||||
definition: ModelsAsDataLanguagePredicate<T>,
|
||||
) {
|
||||
if (methods.length === 0) {
|
||||
return " []";
|
||||
@@ -34,38 +41,92 @@ function createDataProperty(
|
||||
.join("\n")}`;
|
||||
}
|
||||
|
||||
function createExtensions<T>(
|
||||
language: QueryLanguage,
|
||||
methods: readonly T[],
|
||||
definition: ModelsAsDataLanguagePredicate<T> | undefined,
|
||||
) {
|
||||
if (!definition) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return ` - addsTo:
|
||||
pack: codeql/${language}-all
|
||||
extensible: ${definition.extensiblePredicate}
|
||||
data:${createDataProperty(methods, definition)}
|
||||
`;
|
||||
}
|
||||
|
||||
export function createDataExtensionYaml(
|
||||
language: QueryLanguage,
|
||||
modeledMethods: readonly ModeledMethod[],
|
||||
) {
|
||||
const modelsAsDataLanguage = getModelsAsDataLanguage(language);
|
||||
|
||||
const methodsByType: Record<
|
||||
Exclude<ModeledMethodType, "none">,
|
||||
ModeledMethod[]
|
||||
> = {
|
||||
source: [],
|
||||
sink: [],
|
||||
summary: [],
|
||||
neutral: [],
|
||||
};
|
||||
const methodsByType = {
|
||||
source: [] as SourceModeledMethod[],
|
||||
sink: [] as SinkModeledMethod[],
|
||||
summary: [] as SummaryModeledMethod[],
|
||||
neutral: [] as NeutralModeledMethod[],
|
||||
} satisfies Record<keyof ModelsAsDataLanguagePredicates, ModeledMethod[]>;
|
||||
|
||||
for (const modeledMethod of modeledMethods) {
|
||||
if (modeledMethod?.type && modeledMethod.type !== "none") {
|
||||
methodsByType[modeledMethod.type].push(modeledMethod);
|
||||
if (!modeledMethod?.type || modeledMethod.type === "none") {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (modeledMethod.type) {
|
||||
case "source":
|
||||
methodsByType.source.push(modeledMethod);
|
||||
break;
|
||||
case "sink":
|
||||
methodsByType.sink.push(modeledMethod);
|
||||
break;
|
||||
case "summary":
|
||||
methodsByType.summary.push(modeledMethod);
|
||||
break;
|
||||
case "neutral":
|
||||
methodsByType.neutral.push(modeledMethod);
|
||||
break;
|
||||
default:
|
||||
assertNever(modeledMethod);
|
||||
}
|
||||
}
|
||||
|
||||
const extensions = Object.entries(modelsAsDataLanguage.predicates).map(
|
||||
([type, definition]) => ` - addsTo:
|
||||
pack: codeql/${language}-all
|
||||
extensible: ${definition.extensiblePredicate}
|
||||
data:${createDataProperty(
|
||||
methodsByType[type as Exclude<ModeledMethodType, "none">],
|
||||
definition,
|
||||
)}
|
||||
`,
|
||||
);
|
||||
const extensions = Object.keys(methodsByType)
|
||||
.map((typeKey) => {
|
||||
const type = typeKey as keyof ModelsAsDataLanguagePredicates;
|
||||
|
||||
switch (type) {
|
||||
case "source":
|
||||
return createExtensions(
|
||||
language,
|
||||
methodsByType.source,
|
||||
modelsAsDataLanguage.predicates.source,
|
||||
);
|
||||
case "sink":
|
||||
return createExtensions(
|
||||
language,
|
||||
methodsByType.sink,
|
||||
modelsAsDataLanguage.predicates.sink,
|
||||
);
|
||||
case "summary":
|
||||
return createExtensions(
|
||||
language,
|
||||
methodsByType.summary,
|
||||
modelsAsDataLanguage.predicates.summary,
|
||||
);
|
||||
case "neutral":
|
||||
return createExtensions(
|
||||
language,
|
||||
methodsByType.neutral,
|
||||
modelsAsDataLanguage.predicates.neutral,
|
||||
);
|
||||
default:
|
||||
assertNever(type);
|
||||
}
|
||||
})
|
||||
.filter((extensions) => extensions !== "");
|
||||
|
||||
return `extensions:
|
||||
${extensions.join("\n")}`;
|
||||
|
||||
@@ -4,7 +4,13 @@ import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { MethodModeling as MethodModelingComponent } from "../../view/method-modeling/MethodModeling";
|
||||
import { createMethod } from "../../../test/factories/model-editor/method-factories";
|
||||
import { createModeledMethod } from "../../../test/factories/model-editor/modeled-method-factories";
|
||||
import {
|
||||
createNeutralModeledMethod,
|
||||
createSinkModeledMethod,
|
||||
createSourceModeledMethod,
|
||||
} from "../../../test/factories/model-editor/modeled-method-factories";
|
||||
import { QueryLanguage } from "../../common/query-language";
|
||||
|
||||
export default {
|
||||
title: "Method Modeling/Method Modeling",
|
||||
component: MethodModelingComponent,
|
||||
@@ -15,9 +21,11 @@ const Template: StoryFn<typeof MethodModelingComponent> = (args) => (
|
||||
);
|
||||
|
||||
const method = createMethod();
|
||||
const language = QueryLanguage.Java;
|
||||
|
||||
export const MethodUnmodeled = Template.bind({});
|
||||
MethodUnmodeled.args = {
|
||||
language,
|
||||
method,
|
||||
modeledMethods: [],
|
||||
modelingStatus: "unmodeled",
|
||||
@@ -25,6 +33,7 @@ MethodUnmodeled.args = {
|
||||
|
||||
export const MethodModeled = Template.bind({});
|
||||
MethodModeled.args = {
|
||||
language,
|
||||
method,
|
||||
modeledMethods: [],
|
||||
modelingStatus: "unsaved",
|
||||
@@ -32,6 +41,7 @@ MethodModeled.args = {
|
||||
|
||||
export const MethodSaved = Template.bind({});
|
||||
MethodSaved.args = {
|
||||
language,
|
||||
method,
|
||||
modeledMethods: [],
|
||||
modelingStatus: "saved",
|
||||
@@ -39,6 +49,7 @@ MethodSaved.args = {
|
||||
|
||||
export const MultipleModelingsUnmodeled = Template.bind({});
|
||||
MultipleModelingsUnmodeled.args = {
|
||||
language,
|
||||
method,
|
||||
modeledMethods: [],
|
||||
showMultipleModels: true,
|
||||
@@ -47,21 +58,22 @@ MultipleModelingsUnmodeled.args = {
|
||||
|
||||
export const MultipleModelingsModeledSingle = Template.bind({});
|
||||
MultipleModelingsModeledSingle.args = {
|
||||
language,
|
||||
method,
|
||||
modeledMethods: [createModeledMethod(method)],
|
||||
modeledMethods: [createSinkModeledMethod(method)],
|
||||
showMultipleModels: true,
|
||||
modelingStatus: "saved",
|
||||
};
|
||||
|
||||
export const MultipleModelingsModeledMultiple = Template.bind({});
|
||||
MultipleModelingsModeledMultiple.args = {
|
||||
language,
|
||||
method,
|
||||
modeledMethods: [
|
||||
createModeledMethod(method),
|
||||
createModeledMethod({
|
||||
createSinkModeledMethod(method),
|
||||
createSourceModeledMethod({
|
||||
...method,
|
||||
type: "source",
|
||||
input: "",
|
||||
output: "ReturnValue",
|
||||
kind: "remote",
|
||||
}),
|
||||
@@ -72,13 +84,11 @@ MultipleModelingsModeledMultiple.args = {
|
||||
|
||||
export const MultipleModelingsValidationFailedNeutral = Template.bind({});
|
||||
MultipleModelingsValidationFailedNeutral.args = {
|
||||
language,
|
||||
method,
|
||||
modeledMethods: [
|
||||
createModeledMethod(method),
|
||||
createModeledMethod({
|
||||
...method,
|
||||
type: "neutral",
|
||||
}),
|
||||
createSinkModeledMethod(method),
|
||||
createNeutralModeledMethod(method),
|
||||
],
|
||||
showMultipleModels: true,
|
||||
modelingStatus: "unsaved",
|
||||
@@ -86,17 +96,16 @@ MultipleModelingsValidationFailedNeutral.args = {
|
||||
|
||||
export const MultipleModelingsValidationFailedDuplicate = Template.bind({});
|
||||
MultipleModelingsValidationFailedDuplicate.args = {
|
||||
language,
|
||||
method,
|
||||
modeledMethods: [
|
||||
createModeledMethod(method),
|
||||
createModeledMethod({
|
||||
createSinkModeledMethod(method),
|
||||
createSourceModeledMethod({
|
||||
...method,
|
||||
type: "source",
|
||||
input: "",
|
||||
output: "ReturnValue",
|
||||
kind: "remote",
|
||||
}),
|
||||
createModeledMethod(method),
|
||||
createSinkModeledMethod(method),
|
||||
],
|
||||
showMultipleModels: true,
|
||||
modelingStatus: "unsaved",
|
||||
|
||||
@@ -4,9 +4,10 @@ import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { MethodModelingInputs as MethodModelingInputsComponent } from "../../view/method-modeling/MethodModelingInputs";
|
||||
import { createMethod } from "../../../test/factories/model-editor/method-factories";
|
||||
import { createModeledMethod } from "../../../test/factories/model-editor/modeled-method-factories";
|
||||
import { createSinkModeledMethod } from "../../../test/factories/model-editor/modeled-method-factories";
|
||||
import { useState } from "react";
|
||||
import { ModeledMethod } from "../../model-editor/modeled-method";
|
||||
import { QueryLanguage } from "../../common/query-language";
|
||||
|
||||
export default {
|
||||
title: "Method Modeling/Method Modeling Inputs",
|
||||
@@ -32,6 +33,7 @@ const Template: StoryFn<typeof MethodModelingInputsComponent> = (args) => {
|
||||
return (
|
||||
<MethodModelingInputsComponent
|
||||
{...args}
|
||||
language={QueryLanguage.Java}
|
||||
modeledMethod={m}
|
||||
onChange={onChange}
|
||||
/>
|
||||
@@ -39,7 +41,7 @@ const Template: StoryFn<typeof MethodModelingInputsComponent> = (args) => {
|
||||
};
|
||||
|
||||
const method = createMethod();
|
||||
const modeledMethod = createModeledMethod();
|
||||
const modeledMethod = createSinkModeledMethod();
|
||||
|
||||
export const UnmodeledMethod = Template.bind({});
|
||||
UnmodeledMethod.args = {
|
||||
|
||||
@@ -5,8 +5,12 @@ import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { MultipleModeledMethodsPanel as MultipleModeledMethodsPanelComponent } from "../../view/method-modeling/MultipleModeledMethodsPanel";
|
||||
import { createMethod } from "../../../test/factories/model-editor/method-factories";
|
||||
import { createModeledMethod } from "../../../test/factories/model-editor/modeled-method-factories";
|
||||
import {
|
||||
createSinkModeledMethod,
|
||||
createSourceModeledMethod,
|
||||
} from "../../../test/factories/model-editor/modeled-method-factories";
|
||||
import { ModeledMethod } from "../../model-editor/modeled-method";
|
||||
import { QueryLanguage } from "../../common/query-language";
|
||||
|
||||
export default {
|
||||
title: "Method Modeling/Multiple Modeled Methods Panel",
|
||||
@@ -42,28 +46,30 @@ const Template: StoryFn<typeof MultipleModeledMethodsPanelComponent> = (
|
||||
};
|
||||
|
||||
const method = createMethod();
|
||||
const language = QueryLanguage.Java;
|
||||
|
||||
export const Unmodeled = Template.bind({});
|
||||
Unmodeled.args = {
|
||||
language,
|
||||
method,
|
||||
modeledMethods: [],
|
||||
};
|
||||
|
||||
export const Single = Template.bind({});
|
||||
Single.args = {
|
||||
language,
|
||||
method,
|
||||
modeledMethods: [createModeledMethod(method)],
|
||||
modeledMethods: [createSinkModeledMethod(method)],
|
||||
};
|
||||
|
||||
export const Multiple = Template.bind({});
|
||||
Multiple.args = {
|
||||
language,
|
||||
method,
|
||||
modeledMethods: [
|
||||
createModeledMethod(method),
|
||||
createModeledMethod({
|
||||
createSinkModeledMethod(method),
|
||||
createSourceModeledMethod({
|
||||
...method,
|
||||
type: "source",
|
||||
input: "",
|
||||
output: "ReturnValue",
|
||||
kind: "remote",
|
||||
}),
|
||||
|
||||
@@ -149,7 +149,6 @@ LibraryRow.args = {
|
||||
{
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String)",
|
||||
@@ -190,9 +189,7 @@ LibraryRow.args = {
|
||||
"org.sql2o.Query#executeScalar(Class)": [
|
||||
{
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
kind: "summary",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Query#executeScalar(Class)",
|
||||
packageName: "org.sql2o",
|
||||
@@ -204,9 +201,7 @@ LibraryRow.args = {
|
||||
"org.sql2o.Sql2o#Sql2o(String,String,String)": [
|
||||
{
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
kind: "sink",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String,String,String)",
|
||||
packageName: "org.sql2o",
|
||||
|
||||
@@ -219,7 +219,6 @@ ModelEditor.args = {
|
||||
{
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String)",
|
||||
@@ -260,9 +259,7 @@ ModelEditor.args = {
|
||||
"org.sql2o.Query#executeScalar(Class)": [
|
||||
{
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
kind: "sink",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Query#executeScalar(Class)",
|
||||
packageName: "org.sql2o",
|
||||
@@ -274,9 +271,7 @@ ModelEditor.args = {
|
||||
"org.sql2o.Sql2o#Sql2o(String,String,String)": [
|
||||
{
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
kind: "sink",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String,String,String)",
|
||||
packageName: "org.sql2o",
|
||||
|
||||
@@ -13,6 +13,8 @@ import { Codicon } from "../common";
|
||||
import { validateModeledMethods } from "../../model-editor/shared/validation";
|
||||
import { ModeledMethodAlert } from "./ModeledMethodAlert";
|
||||
import { QueryLanguage } from "../../common/query-language";
|
||||
import { createEmptyModeledMethod } from "../../model-editor/modeled-method-empty";
|
||||
import { sendTelemetry } from "../common/telemetry";
|
||||
|
||||
export type MultipleModeledMethodsPanelProps = {
|
||||
language: QueryLanguage;
|
||||
@@ -81,9 +83,11 @@ export const MultipleModeledMethodsPanel = ({
|
||||
|
||||
const handlePreviousClick = useCallback(() => {
|
||||
setSelectedIndex((previousIndex) => previousIndex - 1);
|
||||
sendTelemetry("method-modeling-previous-modeling");
|
||||
}, []);
|
||||
const handleNextClick = useCallback(() => {
|
||||
setSelectedIndex((previousIndex) => previousIndex + 1);
|
||||
sendTelemetry("method-modeling-next-modeling");
|
||||
}, []);
|
||||
|
||||
const validationErrors = useMemo(
|
||||
@@ -92,23 +96,16 @@ export const MultipleModeledMethodsPanel = ({
|
||||
);
|
||||
|
||||
const handleAddClick = useCallback(() => {
|
||||
const newModeledMethod: ModeledMethod = {
|
||||
type: "none",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "manual",
|
||||
signature: method.signature,
|
||||
packageName: method.packageName,
|
||||
typeName: method.typeName,
|
||||
methodName: method.methodName,
|
||||
methodParameters: method.methodParameters,
|
||||
};
|
||||
const newModeledMethod: ModeledMethod = createEmptyModeledMethod(
|
||||
"none",
|
||||
method,
|
||||
);
|
||||
|
||||
const newModeledMethods = [...modeledMethods, newModeledMethod];
|
||||
|
||||
onChange(method.signature, newModeledMethods);
|
||||
selectNewMethod.current = newModeledMethods.length - 1;
|
||||
sendTelemetry("method-modeling-add-model");
|
||||
}, [onChange, modeledMethods, method]);
|
||||
|
||||
const handleRemoveClick = useCallback(() => {
|
||||
@@ -123,6 +120,7 @@ export const MultipleModeledMethodsPanel = ({
|
||||
|
||||
onChange(method.signature, newModeledMethods);
|
||||
setSelectedIndex(newSelectedIndex);
|
||||
sendTelemetry("method-modeling-remove-model");
|
||||
}, [onChange, modeledMethods, selectedIndex, method]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as React from "react";
|
||||
import { render as reactRender, screen } from "@testing-library/react";
|
||||
import { MethodModeling, MethodModelingProps } from "../MethodModeling";
|
||||
import { createMethod } from "../../../../test/factories/model-editor/method-factories";
|
||||
import { createModeledMethod } from "../../../../test/factories/model-editor/modeled-method-factories";
|
||||
import { createSinkModeledMethod } from "../../../../test/factories/model-editor/modeled-method-factories";
|
||||
import { QueryLanguage } from "../../../common/query-language";
|
||||
|
||||
describe(MethodModeling.name, () => {
|
||||
@@ -11,7 +11,7 @@ describe(MethodModeling.name, () => {
|
||||
|
||||
it("renders method modeling panel", () => {
|
||||
const method = createMethod();
|
||||
const modeledMethod = createModeledMethod();
|
||||
const modeledMethod = createSinkModeledMethod();
|
||||
const isModelingInProgress = false;
|
||||
const onChange = jest.fn();
|
||||
|
||||
|
||||
@@ -6,8 +6,12 @@ import {
|
||||
MethodModelingInputsProps,
|
||||
} from "../MethodModelingInputs";
|
||||
import { createMethod } from "../../../../test/factories/model-editor/method-factories";
|
||||
import { createModeledMethod } from "../../../../test/factories/model-editor/modeled-method-factories";
|
||||
import {
|
||||
createMethodSignature,
|
||||
createSinkModeledMethod,
|
||||
} from "../../../../test/factories/model-editor/modeled-method-factories";
|
||||
import { QueryLanguage } from "../../../common/query-language";
|
||||
import { createEmptyModeledMethod } from "../../../model-editor/modeled-method-empty";
|
||||
|
||||
describe(MethodModelingInputs.name, () => {
|
||||
const render = (props: MethodModelingInputsProps) =>
|
||||
@@ -15,7 +19,7 @@ describe(MethodModelingInputs.name, () => {
|
||||
|
||||
const language = QueryLanguage.Java;
|
||||
const method = createMethod();
|
||||
const modeledMethod = createModeledMethod();
|
||||
const modeledMethod = createSinkModeledMethod();
|
||||
const isModelingInProgress = false;
|
||||
const onChange = jest.fn();
|
||||
|
||||
@@ -76,9 +80,10 @@ describe(MethodModelingInputs.name, () => {
|
||||
onChange,
|
||||
});
|
||||
|
||||
const updatedModeledMethod = createModeledMethod({
|
||||
type: "source",
|
||||
});
|
||||
const updatedModeledMethod = createEmptyModeledMethod(
|
||||
"source",
|
||||
createMethodSignature(),
|
||||
);
|
||||
|
||||
rerender(
|
||||
<MethodModelingInputs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { render as reactRender, screen } from "@testing-library/react";
|
||||
import { createMethod } from "../../../../test/factories/model-editor/method-factories";
|
||||
import { createModeledMethod } from "../../../../test/factories/model-editor/modeled-method-factories";
|
||||
import { createSinkModeledMethod } from "../../../../test/factories/model-editor/modeled-method-factories";
|
||||
import {
|
||||
ModeledMethodsPanel,
|
||||
ModeledMethodsPanelProps,
|
||||
@@ -14,7 +14,7 @@ describe(ModeledMethodsPanel.name, () => {
|
||||
|
||||
const language = QueryLanguage.Java;
|
||||
const method = createMethod();
|
||||
const modeledMethods = [createModeledMethod(), createModeledMethod()];
|
||||
const modeledMethods = [createSinkModeledMethod(), createSinkModeledMethod()];
|
||||
const isModelingInProgress = false;
|
||||
const onChange = jest.fn();
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import * as React from "react";
|
||||
import { render as reactRender, screen, waitFor } from "@testing-library/react";
|
||||
import { createMethod } from "../../../../test/factories/model-editor/method-factories";
|
||||
import { createModeledMethod } from "../../../../test/factories/model-editor/modeled-method-factories";
|
||||
import {
|
||||
createNoneModeledMethod,
|
||||
createSinkModeledMethod,
|
||||
createSourceModeledMethod,
|
||||
} from "../../../../test/factories/model-editor/modeled-method-factories";
|
||||
import {
|
||||
MultipleModeledMethodsPanel,
|
||||
MultipleModeledMethodsPanelProps,
|
||||
@@ -82,11 +86,10 @@ describe(MultipleModeledMethodsPanel.name, () => {
|
||||
|
||||
describe("with one modeled method", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
createSinkModeledMethod({
|
||||
...method,
|
||||
type: "sink",
|
||||
input: "Argument[this]",
|
||||
output: "",
|
||||
kind: "path-injection",
|
||||
}),
|
||||
];
|
||||
@@ -164,10 +167,6 @@ describe(MultipleModeledMethodsPanel.name, () => {
|
||||
methodName: method.methodName,
|
||||
methodParameters: method.methodParameters,
|
||||
type: "none",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "manual",
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -201,19 +200,11 @@ describe(MultipleModeledMethodsPanel.name, () => {
|
||||
|
||||
describe("with two modeled methods", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
createSinkModeledMethod({
|
||||
...method,
|
||||
type: "sink",
|
||||
input: "Argument[this]",
|
||||
output: "",
|
||||
kind: "path-injection",
|
||||
}),
|
||||
createModeledMethod({
|
||||
createSourceModeledMethod({
|
||||
...method,
|
||||
type: "source",
|
||||
input: "",
|
||||
output: "ReturnValue",
|
||||
kind: "remote",
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -367,7 +358,6 @@ describe(MultipleModeledMethodsPanel.name, () => {
|
||||
methodName: method.methodName,
|
||||
methodParameters: method.methodParameters,
|
||||
type: "source",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "value",
|
||||
provenance: "manual",
|
||||
@@ -403,7 +393,6 @@ describe(MultipleModeledMethodsPanel.name, () => {
|
||||
methodParameters: method.methodParameters,
|
||||
type: "sink",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "value",
|
||||
provenance: "manual",
|
||||
},
|
||||
@@ -447,10 +436,6 @@ describe(MultipleModeledMethodsPanel.name, () => {
|
||||
methodName: method.methodName,
|
||||
methodParameters: method.methodParameters,
|
||||
type: "none",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "manual",
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -553,24 +538,18 @@ describe(MultipleModeledMethodsPanel.name, () => {
|
||||
|
||||
describe("with three modeled methods", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
createSinkModeledMethod({
|
||||
...method,
|
||||
type: "sink",
|
||||
input: "Argument[this]",
|
||||
output: "",
|
||||
kind: "path-injection",
|
||||
}),
|
||||
createModeledMethod({
|
||||
createSourceModeledMethod({
|
||||
...method,
|
||||
type: "source",
|
||||
input: "",
|
||||
output: "ReturnValue",
|
||||
kind: "remote",
|
||||
}),
|
||||
createModeledMethod({
|
||||
createSourceModeledMethod({
|
||||
...method,
|
||||
type: "source",
|
||||
input: "",
|
||||
output: "ReturnValue",
|
||||
kind: "local",
|
||||
}),
|
||||
@@ -719,19 +698,14 @@ describe(MultipleModeledMethodsPanel.name, () => {
|
||||
|
||||
describe("with 1 modeled and 1 unmodeled method", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
createSinkModeledMethod({
|
||||
...method,
|
||||
type: "sink",
|
||||
input: "Argument[this]",
|
||||
output: "",
|
||||
kind: "path-injection",
|
||||
}),
|
||||
createModeledMethod({
|
||||
createNoneModeledMethod({
|
||||
...method,
|
||||
type: "none",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -823,10 +797,6 @@ describe(MultipleModeledMethodsPanel.name, () => {
|
||||
methodName: method.methodName,
|
||||
methodParameters: method.methodParameters,
|
||||
type: "none",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "manual",
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -834,10 +804,10 @@ describe(MultipleModeledMethodsPanel.name, () => {
|
||||
|
||||
describe("with duplicate modeled methods", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
createSinkModeledMethod({
|
||||
...method,
|
||||
}),
|
||||
createModeledMethod({
|
||||
createSinkModeledMethod({
|
||||
...method,
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -33,6 +33,7 @@ import { canAddNewModeledMethod } from "../../model-editor/shared/multiple-model
|
||||
import { DataGridCell, DataGridRow } from "../common/DataGrid";
|
||||
import { validateModeledMethods } from "../../model-editor/shared/validation";
|
||||
import { ModeledMethodAlert } from "../method-modeling/ModeledMethodAlert";
|
||||
import { createEmptyModeledMethod } from "../../model-editor/modeled-method-empty";
|
||||
|
||||
const ApiOrMethodRow = styled.div`
|
||||
min-height: calc(var(--input-height) * 1px);
|
||||
@@ -165,18 +166,10 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
|
||||
);
|
||||
|
||||
const handleAddModelClick = useCallback(() => {
|
||||
const newModeledMethod: ModeledMethod = {
|
||||
type: "none",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "manual",
|
||||
signature: method.signature,
|
||||
packageName: method.packageName,
|
||||
typeName: method.typeName,
|
||||
methodName: method.methodName,
|
||||
methodParameters: method.methodParameters,
|
||||
};
|
||||
const newModeledMethod: ModeledMethod = createEmptyModeledMethod(
|
||||
"none",
|
||||
method,
|
||||
);
|
||||
const newModeledMethods = [...modeledMethods, newModeledMethod];
|
||||
onChange(method.signature, newModeledMethods);
|
||||
}, [method, modeledMethods, onChange]);
|
||||
@@ -263,7 +256,6 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
|
||||
<DataGridCell>
|
||||
<ModelKindDropdown
|
||||
language={viewState.language}
|
||||
method={method}
|
||||
modeledMethod={modeledMethod}
|
||||
onChange={modeledMethodChangedHandlers[index]}
|
||||
/>
|
||||
@@ -358,20 +350,7 @@ function modeledMethodsToDisplay(
|
||||
viewState: ModelEditorViewState,
|
||||
): ModeledMethod[] {
|
||||
if (modeledMethods.length === 0) {
|
||||
return [
|
||||
{
|
||||
type: "none",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "manual",
|
||||
signature: method.signature,
|
||||
packageName: method.packageName,
|
||||
typeName: method.typeName,
|
||||
methodName: method.methodName,
|
||||
methodParameters: method.methodParameters,
|
||||
},
|
||||
];
|
||||
return [createEmptyModeledMethod("none", method)];
|
||||
}
|
||||
|
||||
if (viewState.showMultipleModels) {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import * as React from "react";
|
||||
import { ChangeEvent, useCallback, useMemo } from "react";
|
||||
import { Dropdown } from "../common/Dropdown";
|
||||
import { ModeledMethod } from "../../model-editor/modeled-method";
|
||||
import {
|
||||
ModeledMethod,
|
||||
modeledMethodSupportsInput,
|
||||
} from "../../model-editor/modeled-method";
|
||||
import { Method, getArgumentsList } from "../../model-editor/method";
|
||||
|
||||
type Props = {
|
||||
@@ -32,14 +35,13 @@ export const ModelInputDropdown = ({
|
||||
);
|
||||
|
||||
const enabled = useMemo(
|
||||
() =>
|
||||
modeledMethod?.type && ["sink", "summary"].includes(modeledMethod?.type),
|
||||
[modeledMethod?.type],
|
||||
() => modeledMethod && modeledMethodSupportsInput(modeledMethod),
|
||||
[modeledMethod],
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e: ChangeEvent<HTMLSelectElement>) => {
|
||||
if (!modeledMethod) {
|
||||
if (!modeledMethod || !modeledMethodSupportsInput(modeledMethod)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -53,9 +55,14 @@ export const ModelInputDropdown = ({
|
||||
[onChange, modeledMethod],
|
||||
);
|
||||
|
||||
const value =
|
||||
modeledMethod && modeledMethodSupportsInput(modeledMethod)
|
||||
? modeledMethod.input
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
value={modeledMethod?.input}
|
||||
value={value}
|
||||
options={options}
|
||||
disabled={!enabled}
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -4,21 +4,19 @@ import type {
|
||||
ModeledMethod,
|
||||
ModeledMethodKind,
|
||||
} from "../../model-editor/modeled-method";
|
||||
import { modeledMethodSupportsKind } from "../../model-editor/modeled-method";
|
||||
import { Dropdown } from "../common/Dropdown";
|
||||
import { Method } from "../../model-editor/method";
|
||||
import { getModelsAsDataLanguage } from "../../model-editor/languages";
|
||||
import { QueryLanguage } from "../../common/query-language";
|
||||
|
||||
type Props = {
|
||||
language: QueryLanguage;
|
||||
method: Method;
|
||||
modeledMethod: ModeledMethod | undefined;
|
||||
onChange: (modeledMethod: ModeledMethod) => void;
|
||||
};
|
||||
|
||||
export const ModelKindDropdown = ({
|
||||
language,
|
||||
method,
|
||||
modeledMethod,
|
||||
onChange,
|
||||
}: Props) => {
|
||||
@@ -44,7 +42,7 @@ export const ModelKindDropdown = ({
|
||||
|
||||
const onChangeKind = useCallback(
|
||||
(kind: ModeledMethodKind) => {
|
||||
if (!modeledMethod) {
|
||||
if (!modeledMethod || !modeledMethodSupportsKind(modeledMethod)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -66,19 +64,26 @@ export const ModelKindDropdown = ({
|
||||
[onChangeKind],
|
||||
);
|
||||
|
||||
const value =
|
||||
modeledMethod && modeledMethodSupportsKind(modeledMethod)
|
||||
? modeledMethod.kind
|
||||
: undefined;
|
||||
|
||||
useEffect(() => {
|
||||
const value = modeledMethod?.kind ?? "";
|
||||
if (!modeledMethod || !modeledMethodSupportsKind(modeledMethod)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (kinds.length === 0 && value !== "") {
|
||||
onChangeKind("");
|
||||
} else if (kinds.length > 0 && !kinds.includes(value)) {
|
||||
} else if (kinds.length > 0 && !kinds.includes(value ?? "")) {
|
||||
onChangeKind(kinds[0]);
|
||||
}
|
||||
}, [modeledMethod?.kind, kinds, onChangeKind]);
|
||||
}, [modeledMethod, value, kinds, onChangeKind]);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
value={modeledMethod?.kind}
|
||||
value={value}
|
||||
options={options}
|
||||
disabled={disabled}
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import * as React from "react";
|
||||
import { ChangeEvent, useCallback, useMemo } from "react";
|
||||
import { Dropdown } from "../common/Dropdown";
|
||||
import { ModeledMethod } from "../../model-editor/modeled-method";
|
||||
import {
|
||||
ModeledMethod,
|
||||
modeledMethodSupportsOutput,
|
||||
} from "../../model-editor/modeled-method";
|
||||
import { Method, getArgumentsList } from "../../model-editor/method";
|
||||
|
||||
type Props = {
|
||||
@@ -33,15 +36,13 @@ export const ModelOutputDropdown = ({
|
||||
);
|
||||
|
||||
const enabled = useMemo(
|
||||
() =>
|
||||
modeledMethod?.type &&
|
||||
["source", "summary"].includes(modeledMethod?.type),
|
||||
[modeledMethod?.type],
|
||||
() => modeledMethod && modeledMethodSupportsOutput(modeledMethod),
|
||||
[modeledMethod],
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e: ChangeEvent<HTMLSelectElement>) => {
|
||||
if (!modeledMethod) {
|
||||
if (!modeledMethod || !modeledMethodSupportsOutput(modeledMethod)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -55,9 +56,14 @@ export const ModelOutputDropdown = ({
|
||||
[onChange, modeledMethod],
|
||||
);
|
||||
|
||||
const value =
|
||||
modeledMethod && modeledMethodSupportsOutput(modeledMethod)
|
||||
? modeledMethod.output
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
value={modeledMethod?.output}
|
||||
value={value}
|
||||
options={options}
|
||||
disabled={!enabled}
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -3,10 +3,13 @@ import { ChangeEvent, useCallback, useMemo } from "react";
|
||||
import { Dropdown } from "../common/Dropdown";
|
||||
import {
|
||||
ModeledMethod,
|
||||
modeledMethodSupportsProvenance,
|
||||
ModeledMethodType,
|
||||
Provenance,
|
||||
} from "../../model-editor/modeled-method";
|
||||
import { Method, getArgumentsList } from "../../model-editor/method";
|
||||
import { createEmptyModeledMethod } from "../../model-editor/modeled-method-empty";
|
||||
import { Mutable } from "../../common/mutable";
|
||||
|
||||
const options: Array<{ value: ModeledMethodType; label: string }> = [
|
||||
{ value: "none", label: "Unmodeled" },
|
||||
@@ -35,25 +38,36 @@ export const ModelTypeDropdown = ({
|
||||
const handleChange = useCallback(
|
||||
(e: ChangeEvent<HTMLSelectElement>) => {
|
||||
let newProvenance: Provenance = "manual";
|
||||
if (modeledMethod?.provenance === "df-generated") {
|
||||
newProvenance = "df-manual";
|
||||
} else if (modeledMethod?.provenance === "ai-generated") {
|
||||
newProvenance = "ai-manual";
|
||||
if (modeledMethod && modeledMethodSupportsProvenance(modeledMethod)) {
|
||||
if (modeledMethod.provenance === "df-generated") {
|
||||
newProvenance = "df-manual";
|
||||
} else if (modeledMethod.provenance === "ai-generated") {
|
||||
newProvenance = "ai-manual";
|
||||
}
|
||||
}
|
||||
|
||||
const updatedModeledMethod: ModeledMethod = {
|
||||
// If there are no arguments, we will default to "Argument[this]"
|
||||
input: argumentsList.length === 0 ? "Argument[this]" : "Argument[0]",
|
||||
output: "ReturnValue",
|
||||
kind: "value",
|
||||
type: e.target.value as ModeledMethodType,
|
||||
provenance: newProvenance,
|
||||
signature: method.signature,
|
||||
packageName: method.packageName,
|
||||
typeName: method.typeName,
|
||||
methodName: method.methodName,
|
||||
methodParameters: method.methodParameters,
|
||||
const emptyModeledMethod = createEmptyModeledMethod(
|
||||
e.target.value as ModeledMethodType,
|
||||
method,
|
||||
);
|
||||
const updatedModeledMethod: Mutable<ModeledMethod> = {
|
||||
...emptyModeledMethod,
|
||||
};
|
||||
if ("input" in updatedModeledMethod) {
|
||||
// If there are no arguments, we will default to "Argument[this]"
|
||||
updatedModeledMethod.input =
|
||||
argumentsList.length === 0 ? "Argument[this]" : "Argument[0]";
|
||||
}
|
||||
if ("output" in updatedModeledMethod) {
|
||||
updatedModeledMethod.output = "ReturnValue";
|
||||
}
|
||||
if ("provenance" in updatedModeledMethod) {
|
||||
updatedModeledMethod.provenance = newProvenance;
|
||||
}
|
||||
if ("kind" in updatedModeledMethod) {
|
||||
updatedModeledMethod.kind = "value";
|
||||
}
|
||||
|
||||
onChange(updatedModeledMethod);
|
||||
},
|
||||
[onChange, method, modeledMethod, argumentsList],
|
||||
|
||||
@@ -27,7 +27,6 @@ describe(LibraryRow.name, () => {
|
||||
...method,
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
},
|
||||
|
||||
@@ -78,7 +78,6 @@ describe(MethodRow.name, () => {
|
||||
expect(onChange).toHaveBeenCalledWith(method.signature, [
|
||||
{
|
||||
type: "source",
|
||||
input: "Argument[0]",
|
||||
output: "ReturnValue",
|
||||
kind: "value",
|
||||
provenance: "manual",
|
||||
@@ -367,10 +366,6 @@ describe(MethodRow.name, () => {
|
||||
modeledMethod,
|
||||
{
|
||||
type: "none",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "manual",
|
||||
signature: method.signature,
|
||||
packageName: method.packageName,
|
||||
typeName: method.typeName,
|
||||
|
||||
@@ -2,28 +2,28 @@ import * as React from "react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { ModelKindDropdown } from "../ModelKindDropdown";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { createMethod } from "../../../../test/factories/model-editor/method-factories";
|
||||
import { createModeledMethod } from "../../../../test/factories/model-editor/modeled-method-factories";
|
||||
import {
|
||||
createNoneModeledMethod,
|
||||
createSinkModeledMethod,
|
||||
createSourceModeledMethod,
|
||||
} from "../../../../test/factories/model-editor/modeled-method-factories";
|
||||
import { QueryLanguage } from "../../../common/query-language";
|
||||
|
||||
describe(ModelKindDropdown.name, () => {
|
||||
const onChange = jest.fn();
|
||||
const method = createMethod();
|
||||
|
||||
beforeEach(() => {
|
||||
onChange.mockReset();
|
||||
});
|
||||
|
||||
it("allows changing the kind", async () => {
|
||||
const modeledMethod = createModeledMethod({
|
||||
type: "source",
|
||||
const modeledMethod = createSourceModeledMethod({
|
||||
kind: "local",
|
||||
});
|
||||
|
||||
render(
|
||||
<ModelKindDropdown
|
||||
language={QueryLanguage.Java}
|
||||
method={method}
|
||||
modeledMethod={modeledMethod}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
@@ -39,16 +39,13 @@ describe(ModelKindDropdown.name, () => {
|
||||
});
|
||||
|
||||
it("resets the kind when changing the supported kinds", () => {
|
||||
const method = createMethod();
|
||||
const modeledMethod = createModeledMethod({
|
||||
type: "source",
|
||||
const modeledMethod = createSourceModeledMethod({
|
||||
kind: "local",
|
||||
});
|
||||
|
||||
const { rerender } = render(
|
||||
<ModelKindDropdown
|
||||
language={QueryLanguage.Java}
|
||||
method={method}
|
||||
modeledMethod={modeledMethod}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
@@ -58,15 +55,13 @@ describe(ModelKindDropdown.name, () => {
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
|
||||
// Changing the type to sink should update the supported kinds
|
||||
const updatedModeledMethod = createModeledMethod({
|
||||
type: "sink",
|
||||
const updatedModeledMethod = createSinkModeledMethod({
|
||||
kind: "local",
|
||||
});
|
||||
|
||||
rerender(
|
||||
<ModelKindDropdown
|
||||
language={QueryLanguage.Java}
|
||||
method={method}
|
||||
modeledMethod={updatedModeledMethod}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
@@ -76,15 +71,14 @@ describe(ModelKindDropdown.name, () => {
|
||||
});
|
||||
|
||||
it("sets the kind when value is undefined", () => {
|
||||
const method = createMethod();
|
||||
const modeledMethod = createModeledMethod({
|
||||
const modeledMethod = createSourceModeledMethod({
|
||||
type: "source",
|
||||
kind: undefined,
|
||||
});
|
||||
|
||||
render(
|
||||
<ModelKindDropdown
|
||||
language={QueryLanguage.Java}
|
||||
method={method}
|
||||
modeledMethod={modeledMethod}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
@@ -99,16 +93,11 @@ describe(ModelKindDropdown.name, () => {
|
||||
});
|
||||
|
||||
it("does not call onChange when unmodeled and the kind is valid", () => {
|
||||
const method = createMethod();
|
||||
const modeledMethod = createModeledMethod({
|
||||
type: "none",
|
||||
kind: "",
|
||||
});
|
||||
const modeledMethod = createNoneModeledMethod();
|
||||
|
||||
render(
|
||||
<ModelKindDropdown
|
||||
language={QueryLanguage.Java}
|
||||
method={method}
|
||||
modeledMethod={modeledMethod}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
@@ -116,28 +105,4 @@ describe(ModelKindDropdown.name, () => {
|
||||
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls onChange when unmodeled and the kind is valid", () => {
|
||||
const method = createMethod();
|
||||
const modeledMethod = createModeledMethod({
|
||||
type: "none",
|
||||
kind: "local",
|
||||
});
|
||||
|
||||
render(
|
||||
<ModelKindDropdown
|
||||
language={QueryLanguage.Java}
|
||||
method={method}
|
||||
modeledMethod={modeledMethod}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
kind: "",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,7 +53,6 @@ describe(ModeledMethodDataGrid.name, () => {
|
||||
...method1,
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
},
|
||||
|
||||
@@ -53,7 +53,6 @@ describe(ModeledMethodsList.name, () => {
|
||||
...method1,
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
},
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import { ModeledMethod } from "../../../src/model-editor/modeled-method";
|
||||
import {
|
||||
NeutralModeledMethod,
|
||||
NoneModeledMethod,
|
||||
SinkModeledMethod,
|
||||
SourceModeledMethod,
|
||||
SummaryModeledMethod,
|
||||
} from "../../../src/model-editor/modeled-method";
|
||||
import { MethodSignature } from "../../../src/model-editor/method";
|
||||
|
||||
export function createModeledMethod(
|
||||
data: Partial<ModeledMethod> = {},
|
||||
): ModeledMethod {
|
||||
export function createMethodSignature(
|
||||
data: Partial<MethodSignature> = {},
|
||||
): MethodSignature {
|
||||
return {
|
||||
libraryVersion: "1.6.0",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
@@ -10,11 +17,68 @@ export function createModeledMethod(
|
||||
typeName: "Connection",
|
||||
methodName: "createQuery",
|
||||
methodParameters: "(String)",
|
||||
...data,
|
||||
};
|
||||
}
|
||||
|
||||
export function createNoneModeledMethod(
|
||||
data: Partial<NoneModeledMethod> = {},
|
||||
): NoneModeledMethod {
|
||||
return {
|
||||
...createMethodSignature(),
|
||||
type: "none",
|
||||
...data,
|
||||
};
|
||||
}
|
||||
|
||||
export function createSinkModeledMethod(
|
||||
data: Partial<SinkModeledMethod> = {},
|
||||
): SinkModeledMethod {
|
||||
return {
|
||||
...createMethodSignature(),
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "path-injection",
|
||||
provenance: "manual",
|
||||
...data,
|
||||
};
|
||||
}
|
||||
|
||||
export function createSourceModeledMethod(
|
||||
data: Partial<SourceModeledMethod> = {},
|
||||
): SourceModeledMethod {
|
||||
return {
|
||||
...createMethodSignature(),
|
||||
type: "source",
|
||||
output: "ReturnValue",
|
||||
kind: "remote",
|
||||
provenance: "manual",
|
||||
...data,
|
||||
};
|
||||
}
|
||||
|
||||
export function createSummaryModeledMethod(
|
||||
data: Partial<SummaryModeledMethod> = {},
|
||||
): SummaryModeledMethod {
|
||||
return {
|
||||
...createMethodSignature(),
|
||||
type: "summary",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "taint",
|
||||
provenance: "manual",
|
||||
...data,
|
||||
};
|
||||
}
|
||||
|
||||
export function createNeutralModeledMethod(
|
||||
data: Partial<NeutralModeledMethod> = {},
|
||||
): NeutralModeledMethod {
|
||||
return {
|
||||
...createMethodSignature(),
|
||||
type: "neutral",
|
||||
kind: "summary",
|
||||
provenance: "manual",
|
||||
...data,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -103,9 +103,7 @@ describe("getCandidates", () => {
|
||||
"org.my.A#x()": [
|
||||
{
|
||||
type: "neutral",
|
||||
kind: "",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "sink",
|
||||
provenance: "manual",
|
||||
signature: "org.my.A#x()",
|
||||
packageName: "org.my",
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { createNoneModeledMethod } from "../../factories/model-editor/modeled-method-factories";
|
||||
import {
|
||||
ModeledMethod,
|
||||
modeledMethodSupportsInput,
|
||||
modeledMethodSupportsKind,
|
||||
modeledMethodSupportsOutput,
|
||||
modeledMethodSupportsProvenance,
|
||||
} from "../../../src/model-editor/modeled-method";
|
||||
|
||||
describe("modeledMethodSupportsKind", () => {
|
||||
const modeledMethod = createNoneModeledMethod() as ModeledMethod;
|
||||
|
||||
it("can access the kind property", () => {
|
||||
// These are more type tests than unit tests, but they're still useful.
|
||||
if (modeledMethodSupportsKind(modeledMethod)) {
|
||||
expect(modeledMethod.kind).not.toBeUndefined(); // never hit
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("modeledMethodSupportsInput", () => {
|
||||
const modeledMethod = createNoneModeledMethod() as ModeledMethod;
|
||||
|
||||
it("can access the input property", () => {
|
||||
// These are more type tests than unit tests, but they're still useful.
|
||||
if (modeledMethodSupportsInput(modeledMethod)) {
|
||||
expect(modeledMethod.input).not.toBeUndefined(); // never hit
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("modeledMethodSupportsOutput", () => {
|
||||
const modeledMethod = createNoneModeledMethod() as ModeledMethod;
|
||||
|
||||
it("can access the output property", () => {
|
||||
// These are more type tests than unit tests, but they're still useful.
|
||||
if (modeledMethodSupportsOutput(modeledMethod)) {
|
||||
expect(modeledMethod.output).not.toBeUndefined(); // never hit
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("modeledMethodSupportsProvenance", () => {
|
||||
const modeledMethod = createNoneModeledMethod() as ModeledMethod;
|
||||
|
||||
it("can access the provenance property", () => {
|
||||
// These are more type tests than unit tests, but they're still useful.
|
||||
if (modeledMethodSupportsProvenance(modeledMethod)) {
|
||||
expect(modeledMethod.provenance).not.toBeUndefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,15 +1,19 @@
|
||||
import { validateModeledMethods } from "../../../../src/model-editor/shared/validation";
|
||||
import { createModeledMethod } from "../../../factories/model-editor/modeled-method-factories";
|
||||
import {
|
||||
createNeutralModeledMethod,
|
||||
createNoneModeledMethod,
|
||||
createSinkModeledMethod,
|
||||
createSourceModeledMethod,
|
||||
createSummaryModeledMethod,
|
||||
} from "../../../factories/model-editor/modeled-method-factories";
|
||||
|
||||
describe(validateModeledMethods.name, () => {
|
||||
it("should not give an error with valid modeled methods", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
type: "source",
|
||||
createSourceModeledMethod({
|
||||
output: "ReturnValue",
|
||||
}),
|
||||
createModeledMethod({
|
||||
type: "sink",
|
||||
createSinkModeledMethod({
|
||||
input: "Argument[this]",
|
||||
}),
|
||||
];
|
||||
@@ -21,17 +25,13 @@ describe(validateModeledMethods.name, () => {
|
||||
|
||||
it("should not give an error with valid modeled methods and an unmodeled method", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
type: "source",
|
||||
createSourceModeledMethod({
|
||||
output: "ReturnValue",
|
||||
}),
|
||||
createModeledMethod({
|
||||
type: "sink",
|
||||
createSinkModeledMethod({
|
||||
input: "Argument[this]",
|
||||
}),
|
||||
createModeledMethod({
|
||||
type: "none",
|
||||
}),
|
||||
createNoneModeledMethod(),
|
||||
];
|
||||
|
||||
const errors = validateModeledMethods(modeledMethods);
|
||||
@@ -41,20 +41,14 @@ describe(validateModeledMethods.name, () => {
|
||||
|
||||
it("should not give an error with valid modeled methods and multiple unmodeled methods", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
type: "none",
|
||||
}),
|
||||
createModeledMethod({
|
||||
type: "source",
|
||||
createNoneModeledMethod(),
|
||||
createSourceModeledMethod({
|
||||
output: "ReturnValue",
|
||||
}),
|
||||
createModeledMethod({
|
||||
type: "sink",
|
||||
createSinkModeledMethod({
|
||||
input: "Argument[this]",
|
||||
}),
|
||||
createModeledMethod({
|
||||
type: "none",
|
||||
}),
|
||||
createNoneModeledMethod(),
|
||||
];
|
||||
|
||||
const errors = validateModeledMethods(modeledMethods);
|
||||
@@ -63,11 +57,7 @@ describe(validateModeledMethods.name, () => {
|
||||
});
|
||||
|
||||
it("should not give an error with a single neutral model", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
type: "neutral",
|
||||
}),
|
||||
];
|
||||
const modeledMethods = [createNeutralModeledMethod()];
|
||||
|
||||
const errors = validateModeledMethods(modeledMethods);
|
||||
|
||||
@@ -76,12 +66,8 @@ describe(validateModeledMethods.name, () => {
|
||||
|
||||
it("should not give an error with a neutral model and an unmodeled method", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
type: "neutral",
|
||||
}),
|
||||
createModeledMethod({
|
||||
type: "none",
|
||||
}),
|
||||
createNeutralModeledMethod(),
|
||||
createNoneModeledMethod(),
|
||||
];
|
||||
|
||||
const errors = validateModeledMethods(modeledMethods);
|
||||
@@ -90,7 +76,10 @@ describe(validateModeledMethods.name, () => {
|
||||
});
|
||||
|
||||
it("should give an error with exact duplicate modeled methods", () => {
|
||||
const modeledMethods = [createModeledMethod(), createModeledMethod()];
|
||||
const modeledMethods = [
|
||||
createSinkModeledMethod(),
|
||||
createSinkModeledMethod(),
|
||||
];
|
||||
|
||||
const errors = validateModeledMethods(modeledMethods);
|
||||
|
||||
@@ -106,10 +95,10 @@ describe(validateModeledMethods.name, () => {
|
||||
|
||||
it("should give an error with duplicate modeled methods with different provenance", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
createSinkModeledMethod({
|
||||
provenance: "df-generated",
|
||||
}),
|
||||
createModeledMethod({
|
||||
createSinkModeledMethod({
|
||||
provenance: "manual",
|
||||
}),
|
||||
];
|
||||
@@ -127,18 +116,21 @@ describe(validateModeledMethods.name, () => {
|
||||
});
|
||||
|
||||
it("should give an error with duplicate modeled methods with different source unused fields", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
type: "source",
|
||||
const modeledMethod1 = createSourceModeledMethod({
|
||||
output: "ReturnValue",
|
||||
...{
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
}),
|
||||
createModeledMethod({
|
||||
type: "source",
|
||||
},
|
||||
});
|
||||
|
||||
const modeledMethod2 = createSourceModeledMethod({
|
||||
output: "ReturnValue",
|
||||
...{
|
||||
input: "Argument[1]",
|
||||
output: "ReturnValue",
|
||||
}),
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
const modeledMethods = [modeledMethod1, modeledMethod2];
|
||||
|
||||
const errors = validateModeledMethods(modeledMethods);
|
||||
|
||||
@@ -153,18 +145,22 @@ describe(validateModeledMethods.name, () => {
|
||||
});
|
||||
|
||||
it("should give an error with duplicate modeled methods with different sink unused fields", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
type: "sink",
|
||||
input: "Argument[this]",
|
||||
const modeledMethod1 = createSinkModeledMethod({
|
||||
type: "sink",
|
||||
input: "Argument[this]",
|
||||
...{
|
||||
output: "ReturnValue",
|
||||
}),
|
||||
createModeledMethod({
|
||||
type: "sink",
|
||||
input: "Argument[this]",
|
||||
},
|
||||
});
|
||||
const modeledMethod2 = createSinkModeledMethod({
|
||||
type: "sink",
|
||||
input: "Argument[this]",
|
||||
...{
|
||||
output: "Argument[this]",
|
||||
}),
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
const modeledMethods = [modeledMethod1, modeledMethod2];
|
||||
|
||||
const errors = validateModeledMethods(modeledMethods);
|
||||
|
||||
@@ -187,16 +183,14 @@ describe(validateModeledMethods.name, () => {
|
||||
};
|
||||
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
type: "sink",
|
||||
createSummaryModeledMethod({
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
...supportedTrue,
|
||||
}),
|
||||
createModeledMethod({
|
||||
type: "sink",
|
||||
createSummaryModeledMethod({
|
||||
input: "Argument[this]",
|
||||
output: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
...supportedFalse,
|
||||
}),
|
||||
];
|
||||
@@ -215,15 +209,19 @@ describe(validateModeledMethods.name, () => {
|
||||
|
||||
it("should give an error with duplicate modeled methods with different neutral unused fields", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
createNeutralModeledMethod({
|
||||
type: "neutral",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
...{
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
},
|
||||
}),
|
||||
createModeledMethod({
|
||||
createNeutralModeledMethod({
|
||||
type: "neutral",
|
||||
input: "Argument[1]",
|
||||
output: "Argument[this]",
|
||||
...{
|
||||
input: "Argument[1]",
|
||||
output: "Argument[this]",
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -241,11 +239,8 @@ describe(validateModeledMethods.name, () => {
|
||||
|
||||
it("should give an error with neutral combined with other models", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
type: "sink",
|
||||
}),
|
||||
createModeledMethod({
|
||||
type: "neutral",
|
||||
createSinkModeledMethod(),
|
||||
createNeutralModeledMethod({
|
||||
kind: "sink",
|
||||
}),
|
||||
];
|
||||
@@ -264,11 +259,8 @@ describe(validateModeledMethods.name, () => {
|
||||
|
||||
it("should not give an error with other neutral combined with other models", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
type: "sink",
|
||||
}),
|
||||
createModeledMethod({
|
||||
type: "neutral",
|
||||
createSinkModeledMethod(),
|
||||
createNeutralModeledMethod({
|
||||
kind: "summary",
|
||||
}),
|
||||
];
|
||||
@@ -280,15 +272,11 @@ describe(validateModeledMethods.name, () => {
|
||||
|
||||
it("should give an error with duplicate neutral combined with other models", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
type: "neutral",
|
||||
createNeutralModeledMethod({
|
||||
kind: "summary",
|
||||
}),
|
||||
createModeledMethod({
|
||||
type: "summary",
|
||||
}),
|
||||
createModeledMethod({
|
||||
type: "neutral",
|
||||
createSummaryModeledMethod(),
|
||||
createNeutralModeledMethod({
|
||||
kind: "summary",
|
||||
}),
|
||||
];
|
||||
@@ -313,18 +301,12 @@ describe(validateModeledMethods.name, () => {
|
||||
|
||||
it("should include unmodeled methods in the index", () => {
|
||||
const modeledMethods = [
|
||||
createModeledMethod({
|
||||
type: "none",
|
||||
}),
|
||||
createModeledMethod({
|
||||
type: "neutral",
|
||||
createNoneModeledMethod(),
|
||||
createNeutralModeledMethod({
|
||||
kind: "sink",
|
||||
}),
|
||||
createModeledMethod({
|
||||
type: "sink",
|
||||
}),
|
||||
createModeledMethod({
|
||||
type: "neutral",
|
||||
createSinkModeledMethod(),
|
||||
createNeutralModeledMethod({
|
||||
kind: "sink",
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -15,7 +15,6 @@ describe("createDataExtensionYaml", () => {
|
||||
{
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "sql",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
@@ -230,7 +229,6 @@ describe("createDataExtensionYamlsForApplicationMode", () => {
|
||||
{
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "sql",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
@@ -243,8 +241,6 @@ describe("createDataExtensionYamlsForApplicationMode", () => {
|
||||
"org.springframework.boot.SpringApplication#run(Class,String[])": [
|
||||
{
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "summary",
|
||||
provenance: "manual",
|
||||
signature:
|
||||
@@ -259,7 +255,6 @@ describe("createDataExtensionYamlsForApplicationMode", () => {
|
||||
{
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi",
|
||||
provenance: "manual",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String,String,String)",
|
||||
@@ -474,7 +469,6 @@ describe("createDataExtensionYamlsForApplicationMode", () => {
|
||||
{
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "sql",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
@@ -487,8 +481,6 @@ describe("createDataExtensionYamlsForApplicationMode", () => {
|
||||
"org.springframework.boot.SpringApplication#run(Class,String[])": [
|
||||
{
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "summary",
|
||||
provenance: "manual",
|
||||
signature:
|
||||
@@ -503,7 +495,6 @@ describe("createDataExtensionYamlsForApplicationMode", () => {
|
||||
{
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi",
|
||||
provenance: "manual",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String,String,String)",
|
||||
@@ -519,8 +510,6 @@ describe("createDataExtensionYamlsForApplicationMode", () => {
|
||||
"org.sql2o.Connection#createQuery(String)": [
|
||||
{
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "summary",
|
||||
provenance: "manual",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
@@ -533,8 +522,6 @@ describe("createDataExtensionYamlsForApplicationMode", () => {
|
||||
"org.sql2o.Query#executeScalar(Class)": [
|
||||
{
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "summary",
|
||||
provenance: "manual",
|
||||
signature: "org.sql2o.Query#executeScalar(Class)",
|
||||
@@ -718,7 +705,6 @@ describe("createDataExtensionYamlsForFrameworkMode", () => {
|
||||
{
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "sql",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
@@ -732,7 +718,6 @@ describe("createDataExtensionYamlsForFrameworkMode", () => {
|
||||
{
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi",
|
||||
provenance: "manual",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String,String,String)",
|
||||
@@ -874,7 +859,6 @@ describe("createDataExtensionYamlsForFrameworkMode", () => {
|
||||
{
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "sql",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
@@ -888,7 +872,6 @@ describe("createDataExtensionYamlsForFrameworkMode", () => {
|
||||
{
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi",
|
||||
provenance: "manual",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String,String,String)",
|
||||
@@ -904,8 +887,6 @@ describe("createDataExtensionYamlsForFrameworkMode", () => {
|
||||
"org.sql2o.Connection#createQuery(String)": [
|
||||
{
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "summary",
|
||||
provenance: "manual",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
@@ -918,8 +899,6 @@ describe("createDataExtensionYamlsForFrameworkMode", () => {
|
||||
"org.sql2o.Query#executeScalar(Class)": [
|
||||
{
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "summary",
|
||||
provenance: "manual",
|
||||
signature: "org.sql2o.Query#executeScalar(Class)",
|
||||
|
||||
@@ -7,6 +7,9 @@ import { Uri, workspace } from "vscode";
|
||||
import { getErrorMessage } from "../../../src/common/helpers-pure";
|
||||
import * as tmp from "tmp";
|
||||
import { mockedObject } from "../utils/mocking.helpers";
|
||||
import { ensureDir, readFile } from "fs-extra";
|
||||
import { load } from "js-yaml";
|
||||
import { QlPackFile } from "../../../src/packaging/qlpack-file";
|
||||
|
||||
describe("QlPackGenerator", () => {
|
||||
let packFolderPath: string;
|
||||
@@ -14,7 +17,11 @@ describe("QlPackGenerator", () => {
|
||||
let exampleQlFilePath: string;
|
||||
let language: string;
|
||||
let generator: QlPackGenerator;
|
||||
let packAddSpy: jest.Mock<any, []>;
|
||||
let packAddSpy: jest.MockedFunction<typeof CodeQLCliServer.prototype.packAdd>;
|
||||
let resolveQlpacksSpy: jest.MockedFunction<
|
||||
typeof CodeQLCliServer.prototype.resolveQlpacks
|
||||
>;
|
||||
let mockCli: CodeQLCliServer;
|
||||
let dir: tmp.DirResult;
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -29,14 +36,17 @@ describe("QlPackGenerator", () => {
|
||||
exampleQlFilePath = join(packFolderPath, "example.ql");
|
||||
|
||||
packAddSpy = jest.fn();
|
||||
const mockCli = mockedObject<CodeQLCliServer>({
|
||||
resolveQlpacksSpy = jest.fn().mockResolvedValue({});
|
||||
mockCli = mockedObject<CodeQLCliServer>({
|
||||
packAdd: packAddSpy,
|
||||
resolveQlpacks: resolveQlpacksSpy,
|
||||
});
|
||||
|
||||
generator = new QlPackGenerator(
|
||||
language as QueryLanguage,
|
||||
mockCli,
|
||||
packFolderPath,
|
||||
packFolderPath,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -71,5 +81,140 @@ describe("QlPackGenerator", () => {
|
||||
expect(existsSync(exampleQlFilePath)).toBe(true);
|
||||
|
||||
expect(packAddSpy).toHaveBeenCalledWith(packFolderPath, language);
|
||||
|
||||
const qlpack = load(
|
||||
await readFile(qlPackYamlFilePath, "utf8"),
|
||||
) as QlPackFile;
|
||||
expect(qlpack).toEqual(
|
||||
expect.objectContaining({
|
||||
name: "getting-started/codeql-extra-queries-ruby",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
describe("when a pack with the same name already exists", () => {
|
||||
beforeEach(() => {
|
||||
resolveQlpacksSpy.mockResolvedValue({
|
||||
"getting-started/codeql-extra-queries-ruby": ["/path/to/pack"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should change the name of the pack", async () => {
|
||||
await generator.generate();
|
||||
|
||||
const qlpack = load(
|
||||
await readFile(qlPackYamlFilePath, "utf8"),
|
||||
) as QlPackFile;
|
||||
expect(qlpack).toEqual(
|
||||
expect.objectContaining({
|
||||
name: "getting-started/codeql-extra-queries-ruby-1",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the folder name is included in the pack name", () => {
|
||||
beforeEach(async () => {
|
||||
const parentFolderPath = join(dir.name, "my-folder");
|
||||
|
||||
packFolderPath = Uri.file(
|
||||
join(parentFolderPath, `test-ql-pack-${language}`),
|
||||
).fsPath;
|
||||
await ensureDir(parentFolderPath);
|
||||
|
||||
qlPackYamlFilePath = join(packFolderPath, "codeql-pack.yml");
|
||||
exampleQlFilePath = join(packFolderPath, "example.ql");
|
||||
|
||||
generator = new QlPackGenerator(
|
||||
language as QueryLanguage,
|
||||
mockCli,
|
||||
packFolderPath,
|
||||
packFolderPath,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("should set the name of the pack", async () => {
|
||||
await generator.generate();
|
||||
|
||||
const qlpack = load(
|
||||
await readFile(qlPackYamlFilePath, "utf8"),
|
||||
) as QlPackFile;
|
||||
expect(qlpack).toEqual(
|
||||
expect.objectContaining({
|
||||
name: "getting-started/codeql-extra-queries-my-folder-ruby",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
describe("when the folder name includes codeql", () => {
|
||||
beforeEach(async () => {
|
||||
const parentFolderPath = join(dir.name, "my-codeql");
|
||||
|
||||
packFolderPath = Uri.file(
|
||||
join(parentFolderPath, `test-ql-pack-${language}`),
|
||||
).fsPath;
|
||||
await ensureDir(parentFolderPath);
|
||||
|
||||
qlPackYamlFilePath = join(packFolderPath, "codeql-pack.yml");
|
||||
exampleQlFilePath = join(packFolderPath, "example.ql");
|
||||
|
||||
generator = new QlPackGenerator(
|
||||
language as QueryLanguage,
|
||||
mockCli,
|
||||
packFolderPath,
|
||||
packFolderPath,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("should set the name of the pack", async () => {
|
||||
await generator.generate();
|
||||
|
||||
const qlpack = load(
|
||||
await readFile(qlPackYamlFilePath, "utf8"),
|
||||
) as QlPackFile;
|
||||
expect(qlpack).toEqual(
|
||||
expect.objectContaining({
|
||||
name: "getting-started/my-codeql-ruby",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the folder name includes queries", () => {
|
||||
beforeEach(async () => {
|
||||
const parentFolderPath = join(dir.name, "my-queries");
|
||||
|
||||
packFolderPath = Uri.file(
|
||||
join(parentFolderPath, `test-ql-pack-${language}`),
|
||||
).fsPath;
|
||||
await ensureDir(parentFolderPath);
|
||||
|
||||
qlPackYamlFilePath = join(packFolderPath, "codeql-pack.yml");
|
||||
exampleQlFilePath = join(packFolderPath, "example.ql");
|
||||
|
||||
generator = new QlPackGenerator(
|
||||
language as QueryLanguage,
|
||||
mockCli,
|
||||
packFolderPath,
|
||||
packFolderPath,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("should set the name of the pack", async () => {
|
||||
await generator.generate();
|
||||
|
||||
const qlpack = load(
|
||||
await readFile(qlPackYamlFilePath, "utf8"),
|
||||
) as QlPackFile;
|
||||
expect(qlpack).toEqual(
|
||||
expect.objectContaining({
|
||||
name: "getting-started/my-queries-ruby",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
import { mockedObject } from "../../../utils/mocking.helpers";
|
||||
import { ModeledMethod } from "../../../../../src/model-editor/modeled-method";
|
||||
import { Mode } from "../../../../../src/model-editor/shared/mode";
|
||||
import { createModeledMethod } from "../../../../factories/model-editor/modeled-method-factories";
|
||||
import { createSinkModeledMethod } from "../../../../factories/model-editor/modeled-method-factories";
|
||||
|
||||
describe("MethodsUsageDataProvider", () => {
|
||||
const mockCliServer = mockedObject<CodeQLCliServer>({});
|
||||
@@ -252,9 +252,11 @@ describe("MethodsUsageDataProvider", () => {
|
||||
unsupportedModeledMethod,
|
||||
];
|
||||
const modeledMethods: Record<string, ModeledMethod[]> = {};
|
||||
modeledMethods[supportedModeledMethod.signature] = [createModeledMethod()];
|
||||
modeledMethods[supportedModeledMethod.signature] = [
|
||||
createSinkModeledMethod(),
|
||||
];
|
||||
modeledMethods[unsupportedModeledMethod.signature] = [
|
||||
createModeledMethod(),
|
||||
createSinkModeledMethod(),
|
||||
];
|
||||
const modifiedMethodSignatures: Set<string> = new Set();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user