Merge branch 'main' into robertbrignull/useScrollIntoView

This commit is contained in:
Robert
2023-08-31 10:37:16 +01:00
committed by GitHub
108 changed files with 1487 additions and 827 deletions

View File

@@ -6,9 +6,11 @@ on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- .github/workflows/cli-test.yml
- extensions/ql-vscode/src/codeql-cli/**
- extensions/ql-vscode/src/language-support/**
- extensions/ql-vscode/src/query-server/**
- extensions/ql-vscode/supported_cli_versions.json
jobs:
find-nightly:
@@ -47,7 +49,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
version: ${{ fromJson(needs.set-matrix.outputs.cli-versions) }}
version: ${{ fromJson(needs.set-matrix.outputs.cli-versions) }}
fail-fast: false
env:
CLI_VERSION: ${{ matrix.version }}

View File

@@ -1,5 +1,5 @@
**/* @github/codeql-vscode-reviewers
**/variant-analysis/ @github/code-scanning-secexp-reviewers
**/databases/ @github/code-scanning-secexp-reviewers
**/data-extensions-editor/ @github/code-scanning-secexp-reviewers
**/model-editor/ @github/code-scanning-secexp-reviewers
**/queries-panel/ @github/code-scanning-secexp-reviewers

View File

@@ -2,6 +2,7 @@
## [UNRELEASED]
- Update how variant analysis results are displayed. For queries with ["path-problem" or "problem" `@kind`](https://codeql.github.com/docs/writing-codeql-queries/metadata-for-codeql-queries/#metadata-properties), you can choose to display the results as rendered alerts or as a table of raw results. For queries with any other `@kind`, the results are displayed as a table. [#2745](https://github.com/github/vscode-codeql/pull/2745) & [#2749](https://github.com/github/vscode-codeql/pull/2749)
- When running variant analyses, don't download artifacts for repositories with no results. [#2736](https://github.com/github/vscode-codeql/pull/2736)
- Group the extension settings, so that they're easier to find in the Settings UI. [#2706](https://github.com/github/vscode-codeql/pull/2706)

View File

@@ -943,8 +943,8 @@
"enablement": "codeql.hasQLSource"
},
{
"command": "codeQL.openDataExtensionsEditor",
"title": "CodeQL: Open Data Extensions Editor"
"command": "codeQL.openModelEditor",
"title": "CodeQL: Open CodeQL Model Editor"
},
{
"command": "codeQL.mockGitHubApiServer.startRecording",
@@ -1415,7 +1415,7 @@
"when": "false"
},
{
"command": "codeQL.openDataExtensionsEditor",
"command": "codeQL.openModelEditor",
"when": "config.codeQL.canary && config.codeQL.dataExtensions.editor"
},
{
@@ -1652,7 +1652,7 @@
},
{
"command": "codeQL.createQuery",
"when": "config.codeQL.codespacesTemplate"
"when": "config.codeQL.codespacesTemplate || config.codeQL.canary && config.codeQL.queriesPanel"
},
{
"command": "codeQLTests.acceptOutputContextTestItem",
@@ -1724,8 +1724,8 @@
],
"panel": [
{
"id": "codeql-model-details",
"title": "CodeQL Model Details",
"id": "codeql-methods-usage",
"title": "CodeQL Methods Usage",
"icon": "media/logo.svg"
}
]
@@ -1759,17 +1759,25 @@
"when": "config.codeQL.canary"
}
],
"codeql-model-details": [
"codeql-methods-usage": [
{
"id": "codeQLModelDetails",
"name": "CodeQL Model Details",
"when": "config.codeQL.canary && codeql.dataExtensionsEditorOpen"
"id": "codeQLMethodsUsage",
"name": "CodeQL Methods Usage",
"when": "config.codeQL.canary && codeql.modelEditorOpen"
}
],
"explorer": [
{
"type": "webview",
"id": "codeQLMethodModeling",
"name": "CodeQL Method Modeling",
"when": "config.codeQL.canary && config.codeQL.modelEditor.methodModelingView && codeql.modelEditorOpen"
}
]
},
"viewsWelcome": [
{
"view": "codeQLModelDetails",
"view": "codeQLMethodsUsage",
"contents": "Loading..."
},
{

View File

@@ -12,7 +12,7 @@ import type {
} from "../variant-analysis/shared/variant-analysis";
import type { QLDebugConfiguration } from "../debugger/debug-configuration";
import type { QueryTreeViewItem } from "../queries-panel/query-tree-view-item";
import type { Usage } from "../data-extensions-editor/external-api-usage";
import type { Usage } from "../model-editor/external-api-usage";
// A command function matching the signature that VS Code calls when
// a command is invoked from a context menu on a TreeView with
@@ -302,9 +302,9 @@ export type PackagingCommands = {
"codeQL.downloadPacks": () => Promise<void>;
};
export type DataExtensionsEditorCommands = {
"codeQL.openDataExtensionsEditor": () => Promise<void>;
"codeQLDataExtensionsEditor.jumpToUsageLocation": (
export type ModelEditorCommands = {
"codeQL.openModelEditor": () => Promise<void>;
"codeQLModelEditor.jumpToUsageLocation": (
usage: Usage,
databaseItem: DatabaseItem,
) => Promise<void>;
@@ -347,7 +347,7 @@ export type AllExtensionCommands = BaseCommands &
AstCfgCommands &
AstViewerCommands &
PackagingCommands &
DataExtensionsEditorCommands &
ModelEditorCommands &
EvalLogViewerCommands &
SummaryLanguageSupportCommands &
Partial<TestUICommands> &

View File

@@ -17,13 +17,10 @@ import {
} from "../variant-analysis/shared/variant-analysis-filter-sort";
import { ErrorLike } from "../common/errors";
import { DataFlowPaths } from "../variant-analysis/shared/data-flow-paths";
import {
ExternalApiUsage,
Usage,
} from "../data-extensions-editor/external-api-usage";
import { ModeledMethod } from "../data-extensions-editor/modeled-method";
import { DataExtensionEditorViewState } from "../data-extensions-editor/shared/view-state";
import { Mode } from "../data-extensions-editor/shared/mode";
import { ExternalApiUsage, Usage } from "../model-editor/external-api-usage";
import { ModeledMethod } from "../model-editor/modeled-method";
import { ModelEditorViewState } from "../model-editor/shared/view-state";
import { Mode } from "../model-editor/shared/mode";
/**
* This module contains types and code that are shared between
@@ -494,8 +491,8 @@ export type ToDataFlowPathsMessage = SetDataFlowPathsMessage;
export type FromDataFlowPathsMessage = CommonFromViewMessages;
interface SetExtensionPackStateMessage {
t: "setDataExtensionEditorViewState";
viewState: DataExtensionEditorViewState;
t: "setModelEditorViewState";
viewState: ModelEditorViewState;
}
interface SetExternalApiUsagesMessage {
@@ -572,14 +569,14 @@ interface HideModeledApisMessage {
hideModeledApis: boolean;
}
export type ToDataExtensionsEditorMessage =
export type ToModelEditorMessage =
| SetExtensionPackStateMessage
| SetExternalApiUsagesMessage
| LoadModeledMethodsMessage
| AddModeledMethodsMessage
| SetInProgressMethodsMessage;
export type FromDataExtensionsEditorMessage =
export type FromModelEditorMessage =
| ViewLoadedMsg
| SwitchModeMessage
| RefreshExternalApiUsages
@@ -592,3 +589,7 @@ export type FromDataExtensionsEditorMessage =
| StopGeneratingExternalApiFromLlmMessage
| ModelDependencyMessage
| HideModeledApisMessage;
export type FromMethodModelingMessage =
| TelemetryMessage
| UnhandledErrorMessage;

View File

@@ -11,13 +11,13 @@ import { join } from "path";
import { DisposableObject, DisposeHandler } from "../disposable-object";
import { tmpDir } from "../../tmp-dir";
import { getHtmlForWebview, WebviewMessage, WebviewView } from "./webview-html";
import { getHtmlForWebview, WebviewMessage, WebviewKind } from "./webview-html";
export type WebviewPanelConfig = {
viewId: string;
title: string;
viewColumn: ViewColumn;
view: WebviewView;
view: WebviewKind;
preserveFocus?: boolean;
iconPath?: Uri | { dark: Uri; light: Uri };
additionalOptions?: WebviewPanelOptions & WebviewOptions;

View File

@@ -2,12 +2,13 @@ import { ExtensionContext, Uri, Webview } from "vscode";
import { randomBytes } from "crypto";
import { EOL } from "os";
export type WebviewView =
export type WebviewKind =
| "results"
| "compare"
| "variant-analysis"
| "data-flow-paths"
| "data-extensions-editor";
| "model-editor"
| "method-modeling";
export interface WebviewMessage {
t: string;
@@ -20,7 +21,7 @@ export interface WebviewMessage {
export function getHtmlForWebview(
ctx: ExtensionContext,
webview: Webview,
view: WebviewView,
view: WebviewKind,
{
allowInlineStyles,
allowWasmEval,

View File

@@ -704,7 +704,6 @@ export function showQueriesPanel(): boolean {
const DATA_EXTENSIONS = new Setting("dataExtensions", ROOT_SETTING);
const LLM_GENERATION = new Setting("llmGeneration", DATA_EXTENSIONS);
const FRAMEWORK_MODE = new Setting("frameworkMode", DATA_EXTENSIONS);
const DISABLE_AUTO_NAME_EXTENSION_PACK = new Setting(
"disableAutoNameExtensionPack",
DATA_EXTENSIONS,
@@ -718,10 +717,6 @@ export function showLlmGeneration(): boolean {
return !!LLM_GENERATION.getValue<boolean>();
}
export function enableFrameworkMode(): boolean {
return !!FRAMEWORK_MODE.getValue<boolean>();
}
export function disableAutoNameExtensionPack(): boolean {
return !!DISABLE_AUTO_NAME_EXTENSION_PACK.getValue<boolean>();
}

View File

@@ -128,7 +128,7 @@ import { getAstCfgCommands } from "./language-support/ast-viewer/ast-cfg-command
import { App } from "./common/app";
import { registerCommandWithErrorHandling } from "./common/vscode/commands";
import { DebuggerUI } from "./debugger/debugger-ui";
import { DataExtensionsEditorModule } from "./data-extensions-editor/data-extensions-editor-module";
import { ModelEditorModule } from "./model-editor/model-editor-module";
import { TestManager } from "./query-testing/test-manager";
import { TestRunner } from "./query-testing/test-runner";
import { TestManagerBase } from "./query-testing/test-manager-base";
@@ -934,15 +934,14 @@ async function activateWithInstalledDistribution(
const debuggerUI = new DebuggerUI(app, localQueries, dbm);
ctx.subscriptions.push(debuggerUI);
const dataExtensionsEditorModule =
await DataExtensionsEditorModule.initialize(
ctx,
app,
dbm,
cliServer,
qs,
tmpDir.name,
);
const modelEditorModule = await ModelEditorModule.initialize(
ctx,
app,
dbm,
cliServer,
qs,
tmpDir.name,
);
void extLogger.log("Initializing QLTest interface.");
@@ -1015,7 +1014,7 @@ async function activateWithInstalledDistribution(
...getPackagingCommands({
cliServer,
}),
...dataExtensionsEditorModule.getCommands(),
...modelEditorModule.getCommands(),
...evalLogViewer.getCommands(),
...summaryLanguageSupport.getCommands(),
...testUiCommands,

View File

@@ -8,7 +8,7 @@ import {
} from "./key-type";
import { CodeQLCliServer } from "../../codeql-cli/cli";
import { DatabaseItem } from "../../databases/local-databases";
import { resolveQueries as resolveLocalQueries } from "../../local-queries/query-resolver";
import { resolveQueriesByLanguagePack as resolveLocalQueries } from "../../local-queries/query-resolver";
import { extLogger } from "../../common/logging/vscode";
import { TeeLogger } from "../../common/logging";
import { CancellationToken } from "vscode";

View File

@@ -72,16 +72,7 @@ async function resolveQueriesFromPacks(
);
}
/**
* Finds the queries with the specified kind and tags in a QLPack.
*
* @param cli The CLI instance to use.
* @param qlpacks The list of packs to search.
* @param name The name of the query to use in error messages.
* @param constraints Constraints on the queries to search for.
* @returns The found queries from the first pack in which any matching queries were found.
*/
export async function resolveQueries(
export async function resolveQueriesByLanguagePack(
cli: CodeQLCliServer,
qlpacks: QlPacksForLanguage,
name: string,
@@ -95,6 +86,24 @@ export async function resolveQueries(
packsToSearch.push(qlpacks.queryPack);
}
return resolveQueries(cli, packsToSearch, name, constraints);
}
/**
* Finds the queries with the specified kind and tags in a QLPack.
*
* @param cli The CLI instance to use.
* @param packsToSearch The list of packs to search.
* @param name The name of the query to use in error messages.
* @param constraints Constraints on the queries to search for.
* @returns The found queries from the first pack in which any matching queries were found.
*/
export async function resolveQueries(
cli: CodeQLCliServer,
packsToSearch: string[],
name: string,
constraints: QueryConstraints,
): Promise<string[]> {
const queries = await resolveQueriesFromPacks(
cli,
packsToSearch,

View File

@@ -0,0 +1,84 @@
import { CancellationToken } from "vscode";
import { CodeQLCliServer } from "../codeql-cli/cli";
import { ProgressCallback } from "../common/vscode/progress";
import { DatabaseItem } from "../databases/local-databases";
import { CoreCompletedQuery, QueryRunner } from "../query-server";
import { createLockFileForStandardQuery } from "./standard-queries";
import { TeeLogger, showAndLogExceptionWithTelemetry } from "../common/logging";
import { QueryResultType } from "../query-server/new-messages";
import { extLogger } from "../common/logging/vscode";
import { telemetryListener } from "../common/vscode/telemetry";
import { redactableError } from "../common/errors";
import { basename } from "path";
type RunQueryOptions = {
cliServer: CodeQLCliServer;
queryRunner: QueryRunner;
databaseItem: DatabaseItem;
queryPath: string;
queryStorageDir: string;
additionalPacks: string[];
extensionPacks: string[] | undefined;
progress: ProgressCallback;
token: CancellationToken;
createLockFile: boolean;
};
export async function runQuery({
cliServer,
queryRunner,
databaseItem,
queryPath,
queryStorageDir,
additionalPacks,
extensionPacks,
progress,
token,
createLockFile,
}: RunQueryOptions): Promise<CoreCompletedQuery | undefined> {
let cleanupLockFile: (() => Promise<void>) | undefined = undefined;
if (createLockFile) {
// Create a lock file for the query. This is required to resolve dependencies and library path for the query.
const { cleanup } = await createLockFileForStandardQuery(
cliServer,
queryPath,
);
cleanupLockFile = cleanup;
}
// Create a query run to execute
const queryRun = queryRunner.createQueryRun(
databaseItem.databaseUri.fsPath,
{
queryPath,
quickEvalPosition: undefined,
quickEvalCountOnly: false,
},
false,
additionalPacks,
extensionPacks,
queryStorageDir,
undefined,
undefined,
);
const completedQuery = await queryRun.evaluate(
progress,
token,
new TeeLogger(queryRunner.logger, queryRun.outputDir.logPath),
);
await cleanupLockFile?.();
if (completedQuery.resultType !== QueryResultType.SUCCESS) {
void showAndLogExceptionWithTelemetry(
extLogger,
telemetryListener,
redactableError`Failed to run ${basename(queryPath)} query: ${
completedQuery.message ?? "No message"
}`,
);
return;
}
return completedQuery;
}

View File

@@ -1,19 +1,10 @@
import { CodeQLCliServer, SourceInfo } from "../codeql-cli/cli";
import { QueryRunner } from "../query-server";
import { CoreCompletedQuery, QueryRunner } from "../query-server";
import { DatabaseItem } from "../databases/local-databases";
import { ProgressCallback } from "../common/vscode/progress";
import * as Sarif from "sarif";
import { qlpackOfDatabase, resolveQueries } from "../local-queries";
import { extLogger } from "../common/logging/vscode";
import { Mode } from "./shared/mode";
import { QlPacksForLanguage } from "../databases/qlpack";
import { createLockFileForStandardQuery } from "../local-queries/standard-queries";
import { CancellationToken, CancellationTokenSource } from "vscode";
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
import { showAndLogExceptionWithTelemetry, TeeLogger } from "../common/logging";
import { QueryResultType } from "../query-server/new-messages";
import { telemetryListener } from "../common/vscode/telemetry";
import { redactableError } from "../common/errors";
import { interpretResultsSarif } from "../query-results";
import { join } from "path";
import { assertNever } from "../common/helpers-pure";
@@ -21,22 +12,10 @@ import { dir } from "tmp-promise";
import { writeFile, outputFile } from "fs-extra";
import { dump as dumpYaml } from "js-yaml";
import { MethodSignature } from "./external-api-usage";
type AutoModelQueryOptions = {
queryTag: string;
mode: Mode;
cliServer: CodeQLCliServer;
queryRunner: QueryRunner;
databaseItem: DatabaseItem;
qlpack: QlPacksForLanguage;
sourceInfo: SourceInfo | undefined;
additionalPacks: string[];
extensionPacks: string[];
queryStorageDir: string;
progress: ProgressCallback;
token: CancellationToken;
};
import { runQuery } from "../local-queries/run-query";
import { QueryMetadata } from "../common/interface-types";
import { CancellationTokenSource } from "vscode";
import { resolveQueries } from "../local-queries";
function modeTag(mode: Mode): string {
switch (mode) {
@@ -49,108 +28,6 @@ function modeTag(mode: Mode): string {
}
}
async function runAutoModelQuery({
queryTag,
mode,
cliServer,
queryRunner,
databaseItem,
qlpack,
sourceInfo,
additionalPacks,
extensionPacks,
queryStorageDir,
progress,
token,
}: AutoModelQueryOptions): Promise<Sarif.Log | undefined> {
// First, resolve the query that we want to run.
// All queries are tagged like this:
// internal extract automodel <mode> <queryTag>
// Example: internal extract automodel framework-mode candidates
const queries = await resolveQueries(
cliServer,
qlpack,
`Extract automodel ${queryTag}`,
{
kind: "problem",
"tags contain all": ["automodel", modeTag(mode), ...queryTag.split(" ")],
},
);
if (queries.length > 1) {
throw new Error(
`Found multiple auto model queries for ${mode} ${queryTag}. Can't continue`,
);
}
if (queries.length === 0) {
throw new Error(
`Did not found any auto model queries for ${mode} ${queryTag}. Can't continue`,
);
}
const queryPath = queries[0];
const { cleanup: cleanupLockFile } = await createLockFileForStandardQuery(
cliServer,
queryPath,
);
// Get metadata for the query. This is required to interpret the results. We already know the kind is problem
// (because of the constraint in resolveQueries), so we don't need any more checks on the metadata.
const metadata = await cliServer.resolveMetadata(queryPath);
const queryRun = queryRunner.createQueryRun(
databaseItem.databaseUri.fsPath,
{
queryPath,
quickEvalPosition: undefined,
quickEvalCountOnly: false,
},
false,
additionalPacks,
extensionPacks,
queryStorageDir,
undefined,
undefined,
);
const completedQuery = await queryRun.evaluate(
progress,
token,
new TeeLogger(queryRunner.logger, queryRun.outputDir.logPath),
);
await cleanupLockFile?.();
if (completedQuery.resultType !== QueryResultType.SUCCESS) {
void showAndLogExceptionWithTelemetry(
extLogger,
telemetryListener,
redactableError`Auto-model query ${queryTag} failed: ${
completedQuery.message ?? "No message"
}`,
);
return;
}
const interpretedResultsPath = join(
queryStorageDir,
`interpreted-results-${queryTag.replaceAll(" ", "-")}-${queryRun.id}.sarif`,
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- We only need the actual SARIF data, not the extra fields added by SarifInterpretationData
const { t, sortState, ...sarif } = await interpretResultsSarif(
cliServer,
metadata,
{
resultsPath: completedQuery.outputDir.bqrsPath,
interpretedResultsPath,
},
sourceInfo,
["--sarif-add-snippets"],
);
return sarif;
}
type AutoModelQueriesOptions = {
mode: Mode;
candidateMethods: MethodSignature[];
@@ -177,7 +54,46 @@ export async function runAutoModelQueries({
progress,
cancellationTokenSource,
}: AutoModelQueriesOptions): Promise<AutoModelQueriesResult | undefined> {
const qlpack = await qlpackOfDatabase(cliServer, databaseItem);
// First, resolve the query that we want to run.
const queryPath = await resolveAutomodelQuery(
cliServer,
databaseItem,
"candidates",
mode,
);
// Generate a pack containing the candidate filters
const filterPackDir = await generateCandidateFilterPack(
databaseItem.language,
candidateMethods,
);
const additionalPacks = [...getOnDiskWorkspaceFolders(), filterPackDir];
const extensionPacks = Object.keys(
await cliServer.resolveQlpacks(additionalPacks, true),
);
// Run the actual query
const completedQuery = await runQuery({
cliServer,
queryRunner,
databaseItem,
queryPath,
queryStorageDir,
additionalPacks,
extensionPacks,
progress,
token: cancellationTokenSource.token,
createLockFile: false,
});
if (!completedQuery) {
return undefined;
}
// Get metadata for the query. This is required to interpret the results. We already know the kind is problem
// (because of the constraint in resolveQueries), so we don't need any more checks on the metadata.
const metadata = await cliServer.resolveMetadata(queryPath);
// CodeQL needs to have access to the database to be able to retrieve the
// snippets from it. The source location prefix is used to determine the
@@ -194,41 +110,53 @@ export async function runAutoModelQueries({
sourceLocationPrefix,
};
// Generate a pack containing the candidate filters
const filterPackDir = await generateCandidateFilterPack(
databaseItem.language,
candidateMethods,
);
const additionalPacks = [...getOnDiskWorkspaceFolders(), filterPackDir];
const extensionPacks = Object.keys(
await cliServer.resolveQlpacks(additionalPacks, true),
);
const candidates = await runAutoModelQuery({
mode,
queryTag: "candidates",
const candidates = await interpretAutomodelResults(
cliServer,
queryRunner,
databaseItem,
qlpack,
completedQuery,
metadata,
sourceInfo,
additionalPacks,
extensionPacks,
queryStorageDir,
progress,
token: cancellationTokenSource.token,
});
if (!candidates) {
return undefined;
}
);
return {
candidates,
};
}
async function resolveAutomodelQuery(
cliServer: CodeQLCliServer,
databaseItem: DatabaseItem,
queryTag: string,
mode: Mode,
): Promise<string> {
const packsToSearch = [`codeql/${databaseItem.language}-automodel-queries`];
// First, resolve the query that we want to run.
// All queries are tagged like this:
// internal extract automodel <mode> <queryTag>
// Example: internal extract automodel framework-mode candidates
const queries = await resolveQueries(
cliServer,
packsToSearch,
`Extract automodel ${queryTag}`,
{
kind: "problem",
"tags contain all": ["automodel", modeTag(mode), ...queryTag.split(" ")],
},
);
if (queries.length > 1) {
throw new Error(
`Found multiple auto model queries for ${mode} ${queryTag}. Can't continue`,
);
}
if (queries.length === 0) {
throw new Error(
`Did not found any auto model queries for ${mode} ${queryTag}. Can't continue`,
);
}
return queries[0];
}
/**
* generateCandidateFilterPack will create a temporary extension pack.
* This pack will contain a filter that will restrict the automodel queries
@@ -250,7 +178,7 @@ export async function generateCandidateFilterPack(
version: "0.0.0",
library: true,
extensionTargets: {
[`codeql/${language}-queries`]: "*",
[`codeql/${language}-automodel-queries`]: "*",
},
dataExtensions: ["filter.yml"],
};
@@ -271,7 +199,7 @@ export async function generateCandidateFilterPack(
extensions: [
{
addsTo: {
pack: `codeql/${language}-queries`,
pack: `codeql/${language}-automodel-queries`,
extensible: "automodelCandidateFilter",
},
data: dataRows,
@@ -284,3 +212,28 @@ export async function generateCandidateFilterPack(
return packDir;
}
async function interpretAutomodelResults(
cliServer: CodeQLCliServer,
completedQuery: CoreCompletedQuery,
metadata: QueryMetadata,
sourceInfo: SourceInfo | undefined,
): Promise<Sarif.Log> {
const interpretedResultsPath = join(
completedQuery.outputDir.querySaveDir,
"results.sarif",
);
const { ...sarif } = await interpretResultsSarif(
cliServer,
metadata,
{
resultsPath: completedQuery.outputDir.bqrsPath,
interpretedResultsPath,
},
sourceInfo,
["--sarif-add-snippets"],
);
return sarif;
}

View File

@@ -1,25 +1,26 @@
import { CoreCompletedQuery, QueryRunner } from "../query-server";
import { QueryRunner } from "../query-server";
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
import { extLogger } from "../common/logging/vscode";
import { showAndLogExceptionWithTelemetry, TeeLogger } from "../common/logging";
import { showAndLogExceptionWithTelemetry } from "../common/logging";
import { CancellationToken } from "vscode";
import { CodeQLCliServer } from "../codeql-cli/cli";
import { DatabaseItem } from "../databases/local-databases";
import { ProgressCallback } from "../common/vscode/progress";
import { QueryResultType } from "../query-server/new-messages";
import { redactableError } from "../common/errors";
import { telemetryListener } from "../common/vscode/telemetry";
import { join } from "path";
import { Mode } from "./shared/mode";
import { writeFile } from "fs-extra";
import { Query } from "./queries/query";
import { QueryLanguage } from "../common/query-language";
import { dump } from "js-yaml";
import { fetchExternalApiQueries } from "./queries";
import { ExternalApiUsage } from "./external-api-usage";
import { runQuery } from "../local-queries/run-query";
import { decodeBqrsToExternalApiUsages } from "./bqrs";
type RunQueryOptions = {
cliServer: Pick<CodeQLCliServer, "resolveQlpacks">;
queryRunner: Pick<QueryRunner, "createQueryRun" | "logger">;
databaseItem: Pick<DatabaseItem, "contents" | "databaseUri" | "language">;
cliServer: CodeQLCliServer;
queryRunner: QueryRunner;
databaseItem: DatabaseItem;
queryStorageDir: string;
queryDir: string;
@@ -27,39 +28,37 @@ type RunQueryOptions = {
token: CancellationToken;
};
export async function setUpPack(
export async function prepareExternalApiQuery(
queryDir: string,
query: Query,
language: QueryLanguage,
) {
Object.values(Mode).map(async (mode) => {
const queryFile = join(
queryDir,
`FetchExternalApis${mode.charAt(0).toUpperCase() + mode.slice(1)}Mode.ql`,
): Promise<boolean> {
// Resolve the query that we want to run.
const query = fetchExternalApiQueries[language];
if (!query) {
void showAndLogExceptionWithTelemetry(
extLogger,
telemetryListener,
redactableError`No external API usage query found for language ${language}`,
);
return false;
}
// Create the query file.
Object.values(Mode).map(async (mode) => {
const queryFile = join(queryDir, queryNameFromMode(mode));
await writeFile(queryFile, query[`${mode}ModeQuery`], "utf8");
});
// Create any dependencies
if (query.dependencies) {
for (const [filename, contents] of Object.entries(query.dependencies)) {
const dependencyFile = join(queryDir, filename);
await writeFile(dependencyFile, contents, "utf8");
}
}
const syntheticQueryPack = {
name: "codeql/external-api-usage",
version: "0.0.0",
dependencies: {
[`codeql/${language}-all`]: "*",
},
};
const qlpackFile = join(queryDir, "codeql-pack.yml");
await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8");
return true;
}
export async function runQuery(
export async function runExternalApiQueries(
mode: Mode,
{
cliServer,
@@ -70,7 +69,7 @@ export async function runQuery(
progress,
token,
}: RunQueryOptions,
): Promise<CoreCompletedQuery | undefined> {
): Promise<ExternalApiUsage[] | undefined> {
// The below code is temporary to allow for rapid prototyping of the queries. Once the queries are stabilized, we will
// move these queries into the `github/codeql` repository and use them like any other contextual (e.g. AST) queries.
// This is intentionally not pretty code, as it will be removed soon.
@@ -82,44 +81,49 @@ export async function runQuery(
await cliServer.resolveQlpacks(additionalPacks, true),
);
const queryFile = join(
queryDir,
`FetchExternalApis${mode.charAt(0).toUpperCase() + mode.slice(1)}Mode.ql`,
);
const queryPath = join(queryDir, queryNameFromMode(mode));
const queryRun = queryRunner.createQueryRun(
databaseItem.databaseUri.fsPath,
{
queryPath: queryFile,
quickEvalPosition: undefined,
quickEvalCountOnly: false,
},
false,
getOnDiskWorkspaceFolders(),
extensionPacks,
// Run the actual query
const completedQuery = await runQuery({
cliServer,
queryRunner,
databaseItem,
queryPath,
queryStorageDir,
undefined,
undefined,
);
const completedQuery = await queryRun.evaluate(
additionalPacks,
extensionPacks,
progress,
token,
new TeeLogger(queryRunner.logger, queryRun.outputDir.logPath),
);
// We need to create a lock file, because the query is inside our own pack
createLockFile: true,
});
if (completedQuery.resultType !== QueryResultType.SUCCESS) {
void showAndLogExceptionWithTelemetry(
extLogger,
telemetryListener,
redactableError`External API usage query failed: ${
completedQuery.message ?? "No message"
}`,
);
if (!completedQuery) {
return;
}
return completedQuery;
// Read the results and covert to internal representation
progress({
message: "Decoding results",
step: 1100,
maxStep: 1500,
});
const bqrsChunk = await readQueryResults({
cliServer,
bqrsPath: completedQuery.outputDir.bqrsPath,
});
if (!bqrsChunk) {
return;
}
progress({
message: "Finalizing results",
step: 1450,
maxStep: 1500,
});
return decodeBqrsToExternalApiUsages(bqrsChunk);
}
type GetResultsOptions = {
@@ -145,3 +149,9 @@ export async function readQueryResults({
return cliServer.bqrsDecode(bqrsPath, resultSet.name);
}
function queryNameFromMode(mode: Mode): string {
return `FetchExternalApis${
mode.charAt(0).toUpperCase() + mode.slice(1)
}Mode.ql`;
}

View File

@@ -3,19 +3,16 @@ import { DatabaseItem } from "../databases/local-databases";
import { basename } from "path";
import { QueryRunner } from "../query-server";
import { CodeQLCliServer } from "../codeql-cli/cli";
import { showAndLogExceptionWithTelemetry, TeeLogger } from "../common/logging";
import { showAndLogExceptionWithTelemetry } from "../common/logging";
import { extLogger } from "../common/logging/vscode";
import { extensiblePredicateDefinitions } from "./predicates";
import { ProgressCallback } from "../common/vscode/progress";
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
import { ModeledMethod, ModeledMethodType } from "./modeled-method";
import { redactableError } from "../common/errors";
import { QueryResultType } from "../query-server/new-messages";
import { file } from "tmp-promise";
import { writeFile } from "fs-extra";
import { dump } from "js-yaml";
import { qlpackOfDatabase } from "../local-queries";
import { telemetryListener } from "../common/vscode/telemetry";
import { runQuery } from "../local-queries/run-query";
import { resolveQueries } from "../local-queries";
type FlowModelOptions = {
cliServer: CodeQLCliServer;
@@ -27,44 +24,78 @@ type FlowModelOptions = {
onResults: (results: ModeledMethod[]) => void | Promise<void>;
};
async function resolveQueries(
export async function runFlowModelQueries({
onResults,
...options
}: FlowModelOptions) {
const queries = await resolveFlowQueries(
options.cliServer,
options.databaseItem,
);
const queriesByBasename: Record<string, string> = {};
for (const query of queries) {
queriesByBasename[basename(query)] = query;
}
const summaryResults = await runSingleFlowQuery(
"summary",
queriesByBasename["CaptureSummaryModels.ql"],
0,
options,
);
if (summaryResults) {
await onResults(summaryResults);
}
const sinkResults = await runSingleFlowQuery(
"sink",
queriesByBasename["CaptureSinkModels.ql"],
1,
options,
);
if (sinkResults) {
await onResults(sinkResults);
}
const sourceResults = await runSingleFlowQuery(
"source",
queriesByBasename["CaptureSourceModels.ql"],
2,
options,
);
if (sourceResults) {
await onResults(sourceResults);
}
const neutralResults = await runSingleFlowQuery(
"neutral",
queriesByBasename["CaptureNeutralModels.ql"],
3,
options,
);
if (neutralResults) {
await onResults(neutralResults);
}
}
async function resolveFlowQueries(
cliServer: CodeQLCliServer,
databaseItem: DatabaseItem,
): Promise<string[]> {
const qlpacks = await qlpackOfDatabase(cliServer, databaseItem);
const packsToSearch = [`codeql/${databaseItem.language}-queries`];
const packsToSearch: string[] = [];
// The CLI can handle both library packs and query packs, so search both packs in order.
packsToSearch.push(qlpacks.dbschemePack);
if (qlpacks.queryPack !== undefined) {
packsToSearch.push(qlpacks.queryPack);
}
const suiteFile = (
await file({
postfix: ".qls",
})
).path;
const suiteYaml = [];
for (const qlpack of packsToSearch) {
suiteYaml.push({
from: qlpack,
queries: ".",
include: {
"tags contain": "modelgenerator",
},
});
}
await writeFile(suiteFile, dump(suiteYaml), "utf8");
return await cliServer.resolveQueriesInSuite(
suiteFile,
getOnDiskWorkspaceFolders(),
return await resolveQueries(
cliServer,
packsToSearch,
"flow model generator",
{
"tags contain": ["modelgenerator"],
},
);
}
async function getModeledMethodsFromFlow(
async function runSingleFlowQuery(
type: Exclude<ModeledMethodType, "none">,
queryPath: string | undefined,
queryStep: number,
@@ -77,6 +108,7 @@ async function getModeledMethodsFromFlow(
token,
}: Omit<FlowModelOptions, "onResults">,
): Promise<ModeledMethod[]> {
// Check that the right query was found
if (queryPath === undefined) {
void showAndLogExceptionWithTelemetry(
extLogger,
@@ -86,45 +118,33 @@ async function getModeledMethodsFromFlow(
return [];
}
const definition = extensiblePredicateDefinitions[type];
const queryRun = queryRunner.createQueryRun(
databaseItem.databaseUri.fsPath,
{
queryPath,
quickEvalPosition: undefined,
quickEvalCountOnly: false,
},
false,
getOnDiskWorkspaceFolders(),
undefined,
// Run the query
const completedQuery = await runQuery({
cliServer,
queryRunner,
databaseItem,
queryPath,
queryStorageDir,
undefined,
undefined,
);
const queryResult = await queryRun.evaluate(
({ step, message }) =>
additionalPacks: getOnDiskWorkspaceFolders(),
extensionPacks: undefined,
progress: ({ step, message }) =>
progress({
message: `Generating ${type} model: ${message}`,
step: queryStep * 1000 + step,
maxStep: 4000,
}),
token,
new TeeLogger(queryRunner.logger, queryRun.outputDir.logPath),
);
if (queryResult.resultType !== QueryResultType.SUCCESS) {
void showAndLogExceptionWithTelemetry(
extLogger,
telemetryListener,
redactableError`Failed to run ${basename(queryPath)} query: ${
queryResult.message ?? "No message"
}`,
);
createLockFile: false,
});
if (!completedQuery) {
return [];
}
const bqrsPath = queryResult.outputDir.bqrsPath;
// Interpret the results
const definition = extensiblePredicateDefinitions[type];
const bqrsPath = completedQuery.outputDir.bqrsPath;
const bqrsInfo = await cliServer.bqrsInfo(bqrsPath);
if (bqrsInfo["result-sets"].length !== 1) {
@@ -154,55 +174,3 @@ async function getModeledMethodsFromFlow(
})
);
}
export async function generateFlowModel({
onResults,
...options
}: FlowModelOptions) {
const queries = await resolveQueries(options.cliServer, options.databaseItem);
const queriesByBasename: Record<string, string> = {};
for (const query of queries) {
queriesByBasename[basename(query)] = query;
}
const summaryResults = await getModeledMethodsFromFlow(
"summary",
queriesByBasename["CaptureSummaryModels.ql"],
0,
options,
);
if (summaryResults) {
await onResults(summaryResults);
}
const sinkResults = await getModeledMethodsFromFlow(
"sink",
queriesByBasename["CaptureSinkModels.ql"],
1,
options,
);
if (sinkResults) {
await onResults(sinkResults);
}
const sourceResults = await getModeledMethodsFromFlow(
"source",
queriesByBasename["CaptureSourceModels.ql"],
2,
options,
);
if (sourceResults) {
await onResults(sourceResults);
}
const neutralResults = await getModeledMethodsFromFlow(
"neutral",
queriesByBasename["CaptureNeutralModels.ql"],
3,
options,
);
if (neutralResults) {
await onResults(neutralResults);
}
}

View File

@@ -0,0 +1,17 @@
import { ExtensionContext, window } from "vscode";
import { DisposableObject } from "../../common/disposable-object";
import { MethodModelingViewProvider } from "./method-modeling-view-provider";
export class MethodModelingPanel extends DisposableObject {
constructor(context: ExtensionContext) {
super();
const provider = new MethodModelingViewProvider(context);
this.push(
window.registerWebviewViewProvider(
MethodModelingViewProvider.viewType,
provider,
),
);
}
}

View File

@@ -0,0 +1,61 @@
import * as vscode from "vscode";
import { WebviewViewProvider } from "vscode";
import { getHtmlForWebview } from "../../common/vscode/webview-html";
import { FromMethodModelingMessage } from "../../common/interface-types";
import { telemetryListener } from "../../common/vscode/telemetry";
import { showAndLogExceptionWithTelemetry } from "../../common/logging/notifications";
import { extLogger } from "../../common/logging/vscode/loggers";
import { redactableError } from "../../common/errors";
export class MethodModelingViewProvider implements WebviewViewProvider {
public static readonly viewType = "codeQLMethodModeling";
constructor(private readonly context: vscode.ExtensionContext) {}
/**
* This is called when a view first becomes visible. This may happen when the view is
* first loaded or when the user hides and then shows a view again.
*/
public resolveWebviewView(
webviewView: vscode.WebviewView,
_context: vscode.WebviewViewResolveContext,
_token: vscode.CancellationToken,
) {
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [this.context.extensionUri],
};
const html = getHtmlForWebview(
this.context,
webviewView.webview,
"method-modeling",
{
allowInlineStyles: true,
allowWasmEval: false,
},
);
webviewView.webview.html = html;
webviewView.webview.onDidReceiveMessage(async (msg) => this.onMessage(msg));
}
private async onMessage(msg: FromMethodModelingMessage): Promise<void> {
switch (msg.t) {
case "telemetry": {
telemetryListener?.sendUIInteraction(msg.action);
break;
}
case "unhandledError":
void showAndLogExceptionWithTelemetry(
extLogger,
telemetryListener,
redactableError(
msg.error,
)`Unhandled error in method modeling view: ${msg.error.message}`,
);
break;
}
}
}

View File

@@ -15,9 +15,9 @@ import { relative } from "path";
import { CodeQLCliServer } from "../../codeql-cli/cli";
import { INITIAL_HIDE_MODELED_APIS_VALUE } from "../shared/hide-modeled-apis";
export class ModelDetailsDataProvider
export class MethodsUsageDataProvider
extends DisposableObject
implements TreeDataProvider<ModelDetailsTreeViewItem>
implements TreeDataProvider<MethodsUsageTreeViewItem>
{
private externalApiUsages: ExternalApiUsage[] = [];
private databaseItem: DatabaseItem | undefined = undefined;
@@ -63,7 +63,7 @@ export class ModelDetailsDataProvider
}
}
getTreeItem(item: ModelDetailsTreeViewItem): TreeItem {
getTreeItem(item: MethodsUsageTreeViewItem): TreeItem {
if (isExternalApiUsage(item)) {
return {
label: `${item.packageName}.${item.typeName}.${item.methodName}${item.methodParameters}`,
@@ -79,7 +79,7 @@ export class ModelDetailsDataProvider
collapsibleState: TreeItemCollapsibleState.None,
command: {
title: "Show usage",
command: "codeQLDataExtensionsEditor.jumpToUsageLocation",
command: "codeQLModelEditor.jumpToUsageLocation",
arguments: [item, this.databaseItem],
},
iconPath: new ThemeIcon("error", new ThemeColor("errorForeground")),
@@ -96,7 +96,7 @@ export class ModelDetailsDataProvider
}
}
getChildren(item?: ModelDetailsTreeViewItem): ModelDetailsTreeViewItem[] {
getChildren(item?: MethodsUsageTreeViewItem): MethodsUsageTreeViewItem[] {
if (item === undefined) {
if (this.hideModeledApis) {
return this.externalApiUsages.filter((api) => !api.supported);
@@ -111,8 +111,8 @@ export class ModelDetailsDataProvider
}
getParent(
item: ModelDetailsTreeViewItem,
): ModelDetailsTreeViewItem | undefined {
item: MethodsUsageTreeViewItem,
): MethodsUsageTreeViewItem | undefined {
if (isExternalApiUsage(item)) {
return undefined;
} else {
@@ -132,10 +132,10 @@ export class ModelDetailsDataProvider
}
}
export type ModelDetailsTreeViewItem = ExternalApiUsage | Usage;
export type MethodsUsageTreeViewItem = ExternalApiUsage | Usage;
function isExternalApiUsage(
item: ModelDetailsTreeViewItem,
item: MethodsUsageTreeViewItem,
): item is ExternalApiUsage {
return (item as any).usages !== undefined;
}

View File

@@ -1,23 +1,23 @@
import { TreeView, window } from "vscode";
import { DisposableObject } from "../../common/disposable-object";
import {
ModelDetailsDataProvider,
ModelDetailsTreeViewItem,
} from "./model-details-data-provider";
MethodsUsageDataProvider,
MethodsUsageTreeViewItem,
} from "./methods-usage-data-provider";
import { ExternalApiUsage, Usage } from "../external-api-usage";
import { DatabaseItem } from "../../databases/local-databases";
import { CodeQLCliServer } from "../../codeql-cli/cli";
export class ModelDetailsPanel extends DisposableObject {
private readonly dataProvider: ModelDetailsDataProvider;
private readonly treeView: TreeView<ModelDetailsTreeViewItem>;
export class MethodsUsagePanel extends DisposableObject {
private readonly dataProvider: MethodsUsageDataProvider;
private readonly treeView: TreeView<MethodsUsageTreeViewItem>;
public constructor(cliServer: CodeQLCliServer) {
super();
this.dataProvider = new ModelDetailsDataProvider(cliServer);
this.dataProvider = new MethodsUsageDataProvider(cliServer);
this.treeView = window.createTreeView("codeQLModelDetails", {
this.treeView = window.createTreeView("codeQLMethodsUsage", {
treeDataProvider: this.dataProvider,
});
this.push(this.treeView);

View File

@@ -1,6 +1,6 @@
import { ExtensionContext } from "vscode";
import { DataExtensionsEditorView } from "./data-extensions-editor-view";
import { DataExtensionsEditorCommands } from "../common/commands";
import { ModelEditorView } from "./model-editor-view";
import { ModelEditorCommands } from "../common/commands";
import { CliVersionConstraint, CodeQLCliServer } from "../codeql-cli/cli";
import { QueryRunner } from "../query-server";
import { DatabaseItem, DatabaseManager } from "../databases/local-databases";
@@ -9,31 +9,25 @@ import { join } from "path";
import { App } from "../common/app";
import { withProgress } from "../common/vscode/progress";
import { pickExtensionPack } from "./extension-pack-picker";
import {
showAndLogErrorMessage,
showAndLogExceptionWithTelemetry,
} from "../common/logging";
import { showAndLogErrorMessage } from "../common/logging";
import { dir } from "tmp-promise";
import { fetchExternalApiQueries } from "./queries";
import { telemetryListener } from "../common/vscode/telemetry";
import { redactableError } from "../common/errors";
import { extLogger } from "../common/logging/vscode";
import { isQueryLanguage } from "../common/query-language";
import { setUpPack } from "./external-api-usage-query";
import { DisposableObject } from "../common/disposable-object";
import { ModelDetailsPanel } from "./model-details/model-details-panel";
import { MethodsUsagePanel } from "./methods-usage/methods-usage-panel";
import { Mode } from "./shared/mode";
import { showResolvableLocation } from "../databases/local-databases/locations";
import { Usage } from "./external-api-usage";
import { setUpPack } from "./model-editor-queries";
import { MethodModelingPanel } from "./method-modeling/method-modeling-panel";
const SUPPORTED_LANGUAGES: string[] = ["java", "csharp"];
export class DataExtensionsEditorModule extends DisposableObject {
export class ModelEditorModule extends DisposableObject {
private readonly queryStorageDir: string;
private readonly modelDetailsPanel: ModelDetailsPanel;
private readonly methodsUsagePanel: MethodsUsagePanel;
private mostRecentlyActiveView: DataExtensionsEditorView | undefined =
undefined;
private mostRecentlyActiveView: ModelEditorView | undefined = undefined;
private constructor(
private readonly ctx: ExtensionContext,
@@ -44,24 +38,22 @@ export class DataExtensionsEditorModule extends DisposableObject {
baseQueryStorageDir: string,
) {
super();
this.queryStorageDir = join(
baseQueryStorageDir,
"data-extensions-editor-results",
);
this.modelDetailsPanel = this.push(new ModelDetailsPanel(cliServer));
this.queryStorageDir = join(baseQueryStorageDir, "model-editor-results");
this.methodsUsagePanel = this.push(new MethodsUsagePanel(cliServer));
this.push(new MethodModelingPanel(ctx));
}
private handleViewBecameActive(view: DataExtensionsEditorView): void {
private handleViewBecameActive(view: ModelEditorView): void {
this.mostRecentlyActiveView = view;
}
private handleViewWasDisposed(view: DataExtensionsEditorView): void {
private handleViewWasDisposed(view: ModelEditorView): void {
if (this.mostRecentlyActiveView === view) {
this.mostRecentlyActiveView = undefined;
}
}
private isMostRecentlyActiveView(view: DataExtensionsEditorView): boolean {
private isMostRecentlyActiveView(view: ModelEditorView): boolean {
return this.mostRecentlyActiveView === view;
}
@@ -72,8 +64,8 @@ export class DataExtensionsEditorModule extends DisposableObject {
cliServer: CodeQLCliServer,
queryRunner: QueryRunner,
queryStorageDir: string,
): Promise<DataExtensionsEditorModule> {
const dataExtensionsEditorModule = new DataExtensionsEditorModule(
): Promise<ModelEditorModule> {
const modelEditorModule = new ModelEditorModule(
ctx,
app,
databaseManager,
@@ -82,13 +74,13 @@ export class DataExtensionsEditorModule extends DisposableObject {
queryStorageDir,
);
await dataExtensionsEditorModule.initialize();
return dataExtensionsEditorModule;
await modelEditorModule.initialize();
return modelEditorModule;
}
public getCommands(): DataExtensionsEditorCommands {
public getCommands(): ModelEditorCommands {
return {
"codeQL.openDataExtensionsEditor": async () => {
"codeQL.openModelEditor": async () => {
const db = this.databaseManager.currentDatabaseItem;
if (!db) {
void showAndLogErrorMessage(this.app.logger, "No database selected");
@@ -102,7 +94,7 @@ export class DataExtensionsEditorModule extends DisposableObject {
) {
void showAndLogErrorMessage(
this.app.logger,
`The data extensions editor is not supported for ${language} databases.`,
`The CodeQL Model Editor is not supported for ${language} databases.`,
);
return;
}
@@ -138,22 +130,14 @@ export class DataExtensionsEditorModule extends DisposableObject {
return;
}
const query = fetchExternalApiQueries[language];
if (!query) {
void showAndLogExceptionWithTelemetry(
extLogger,
telemetryListener,
redactableError`No external API usage query found for language ${language}`,
);
// Create new temporary directory for query files and pack dependencies
const queryDir = (await dir({ unsafeCleanup: true })).path;
const success = await setUpPack(this.cliServer, queryDir, language);
if (!success) {
return;
}
// Create new temporary directory for query files and pack dependencies
const queryDir = (await dir({ unsafeCleanup: true })).path;
await setUpPack(queryDir, query, language);
await this.cliServer.packInstall(queryDir);
const view = new DataExtensionsEditorView(
const view = new ModelEditorView(
this.ctx,
this.app,
this.databaseManager,
@@ -164,8 +148,8 @@ export class DataExtensionsEditorModule extends DisposableObject {
db,
modelFile,
Mode.Application,
this.modelDetailsPanel.setState.bind(this.modelDetailsPanel),
this.modelDetailsPanel.revealItem.bind(this.modelDetailsPanel),
this.methodsUsagePanel.setState.bind(this.methodsUsagePanel),
this.methodsUsagePanel.revealItem.bind(this.methodsUsagePanel),
this.handleViewBecameActive.bind(this),
this.handleViewWasDisposed.bind(this),
this.isMostRecentlyActiveView.bind(this),
@@ -173,11 +157,11 @@ export class DataExtensionsEditorModule extends DisposableObject {
await view.openView();
},
{
title: "Opening Data Extensions Editor",
title: "Opening CodeQL Model Editor",
},
);
},
"codeQLDataExtensionsEditor.jumpToUsageLocation": async (
"codeQLModelEditor.jumpToUsageLocation": async (
usage: Usage,
databaseItem: DatabaseItem,
) => {

View File

@@ -0,0 +1,46 @@
import { join } from "path";
import { QueryLanguage } from "../common/query-language";
import { writeFile } from "fs-extra";
import { dump } from "js-yaml";
import { prepareExternalApiQuery } from "./external-api-usage-queries";
import { CodeQLCliServer } from "../codeql-cli/cli";
/**
* setUpPack sets up a directory to use for the data extension editor queries.
* @param queryDir The directory to set up.
* @param language The language to use for the queries.
* @returns true if the setup was successful, false otherwise.
*/
export async function setUpPack(
cliServer: CodeQLCliServer,
queryDir: string,
language: QueryLanguage,
): Promise<boolean> {
// Create the external API query
const externalApiQuerySuccess = await prepareExternalApiQuery(
queryDir,
language,
);
if (!externalApiQuerySuccess) {
return false;
}
// Set up a synthetic pack so that the query can be resolved later.
const syntheticQueryPack = {
name: "codeql/external-api-usage",
version: "0.0.0",
dependencies: {
[`codeql/${language}-all`]: "*",
},
};
const qlpackFile = join(queryDir, "codeql-pack.yml");
await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8");
await cliServer.packInstall(queryDir);
// Install the other needed query packs
await cliServer.packDownload([`codeql/${language}-queries`]);
await cliServer.packDownload([`codeql/${language}-automodel-queries`]);
return true;
}

View File

@@ -10,8 +10,8 @@ import {
WebviewPanelConfig,
} from "../common/vscode/abstract-webview";
import {
FromDataExtensionsEditorMessage,
ToDataExtensionsEditorMessage,
FromModelEditorMessage,
ToModelEditorMessage,
} from "../common/interface-types";
import { ProgressCallback, withProgress } from "../common/vscode/progress";
import { QueryRunner } from "../query-server";
@@ -22,17 +22,16 @@ import {
import { DatabaseItem, DatabaseManager } from "../databases/local-databases";
import { CodeQLCliServer } from "../codeql-cli/cli";
import { asError, assertNever, getErrorMessage } from "../common/helpers-pure";
import { generateFlowModel } from "./generate-flow-model";
import { runFlowModelQueries } from "./flow-model-queries";
import { promptImportGithubDatabase } from "../databases/database-fetcher";
import { App } from "../common/app";
import { showResolvableLocation } from "../databases/local-databases/locations";
import { decodeBqrsToExternalApiUsages } from "./bqrs";
import { redactableError } from "../common/errors";
import { readQueryResults, runQuery } from "./external-api-usage-query";
import { runExternalApiQueries } from "./external-api-usage-queries";
import { ExternalApiUsage, Usage } from "./external-api-usage";
import { ModeledMethod } from "./modeled-method";
import { ExtensionPack } from "./shared/extension-pack";
import { enableFrameworkMode, showLlmGeneration } from "../config";
import { showLlmGeneration } from "../config";
import { Mode } from "./shared/mode";
import { loadModeledMethods, saveModeledMethods } from "./modeled-method-fs";
import { join } from "path";
@@ -41,9 +40,9 @@ import { getLanguageDisplayName } from "../common/query-language";
import { AutoModeler } from "./auto-modeler";
import { INITIAL_HIDE_MODELED_APIS_VALUE } from "./shared/hide-modeled-apis";
export class DataExtensionsEditorView extends AbstractWebview<
ToDataExtensionsEditorMessage,
FromDataExtensionsEditorMessage
export class ModelEditorView extends AbstractWebview<
ToModelEditorMessage,
FromModelEditorMessage
> {
private readonly autoModeler: AutoModeler;
@@ -61,20 +60,16 @@ export class DataExtensionsEditorView extends AbstractWebview<
private readonly databaseItem: DatabaseItem,
private readonly extensionPack: ExtensionPack,
private mode: Mode,
private readonly updateModelDetailsPanelState: (
private readonly updateMethodsUsagePanelState: (
externalApiUsages: ExternalApiUsage[],
databaseItem: DatabaseItem,
hideModeledApis: boolean,
) => Promise<void>,
private readonly revealItemInDetailsPanel: (usage: Usage) => Promise<void>,
private readonly handleViewBecameActive: (
view: DataExtensionsEditorView,
) => void,
private readonly handleViewWasDisposed: (
view: DataExtensionsEditorView,
) => void,
private readonly revealItemInUsagePanel: (usage: Usage) => Promise<void>,
private readonly handleViewBecameActive: (view: ModelEditorView) => void,
private readonly handleViewWasDisposed: (view: ModelEditorView) => void,
private readonly isMostRecentlyActiveView: (
view: DataExtensionsEditorView,
view: ModelEditorView,
) => boolean,
) {
super(ctx);
@@ -107,7 +102,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
panel.onDidChangeViewState(async () => {
if (panel.active) {
this.handleViewBecameActive(this);
await this.updateModelDetailsPanelState(
await this.updateMethodsUsagePanelState(
this.externalApiUsages,
this.databaseItem,
this.hideModeledApis,
@@ -121,8 +116,8 @@ export class DataExtensionsEditorView extends AbstractWebview<
// so we want to check if there are any others still open.
void this.app.commands.execute(
"setContext",
"codeql.dataExtensionsEditorOpen",
this.isADataExtensionsEditorOpen(),
"codeql.modelEditorOpen",
this.isAModelEditorOpen(),
);
});
@@ -130,31 +125,31 @@ export class DataExtensionsEditorView extends AbstractWebview<
void this.app.commands.execute(
"setContext",
"codeql.dataExtensionsEditorOpen",
"codeql.modelEditorOpen",
true,
);
}
private isADataExtensionsEditorOpen(): boolean {
private isAModelEditorOpen(): boolean {
return window.tabGroups.all.some((tabGroup) =>
tabGroup.tabs.some((tab) => {
const viewType: string | undefined = (tab.input as any)?.viewType;
// The viewType has a prefix, such as "mainThreadWebview-", but if the
// suffix matches that should be enough to identify the view.
return viewType && viewType.endsWith("data-extensions-editor");
return viewType && viewType.endsWith("model-editor");
}),
);
}
protected async getPanelConfig(): Promise<WebviewPanelConfig> {
return {
viewId: "data-extensions-editor",
viewId: "model-editor",
title: `Modeling ${getLanguageDisplayName(
this.extensionPack.language,
)} (${this.extensionPack.name})`,
viewColumn: ViewColumn.Active,
preserveFocus: true,
view: "data-extensions-editor",
view: "model-editor",
iconPath: {
dark: Uri.file(
join(this.ctx.extensionPath, "media/dark/symbol-misc.svg"),
@@ -170,9 +165,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
// Nothing to do here
}
protected async onMessage(
msg: FromDataExtensionsEditorMessage,
): Promise<void> {
protected async onMessage(msg: FromModelEditorMessage): Promise<void> {
switch (msg.t) {
case "viewLoaded":
await this.onWebViewLoaded();
@@ -239,7 +232,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
break;
case "hideModeledApis":
this.hideModeledApis = msg.hideModeledApis;
await this.updateModelDetailsPanelState(
await this.updateMethodsUsagePanelState(
this.externalApiUsages,
this.databaseItem,
this.hideModeledApis,
@@ -265,10 +258,9 @@ export class DataExtensionsEditorView extends AbstractWebview<
this.databaseItem.language === "java" && showLlmGeneration();
await this.postMessage({
t: "setDataExtensionEditorViewState",
t: "setModelEditorViewState",
viewState: {
extensionPack: this.extensionPack,
enableFrameworkMode: enableFrameworkMode(),
showLlmButton,
mode: this.mode,
},
@@ -276,7 +268,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
}
protected async handleJumpToUsage(usage: Usage) {
await this.revealItemInDetailsPanel(usage);
await this.revealItemInUsagePanel(usage);
await showResolvableLocation(usage.url, this.databaseItem, this.app.logger);
}
@@ -304,7 +296,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
async (progress) => {
try {
const cancellationTokenSource = new CancellationTokenSource();
const queryResult = await runQuery(this.mode, {
const queryResult = await runExternalApiQueries(this.mode, {
cliServer: this.cliServer,
queryRunner: this.queryRunner,
databaseItem: this.databaseItem,
@@ -316,35 +308,14 @@ export class DataExtensionsEditorView extends AbstractWebview<
if (!queryResult) {
return;
}
progress({
message: "Decoding results",
step: 1100,
maxStep: 1500,
});
const bqrsChunk = await readQueryResults({
cliServer: this.cliServer,
bqrsPath: queryResult.outputDir.bqrsPath,
});
if (!bqrsChunk) {
return;
}
progress({
message: "Finalizing results",
step: 1450,
maxStep: 1500,
});
this.externalApiUsages = decodeBqrsToExternalApiUsages(bqrsChunk);
this.externalApiUsages = queryResult;
await this.postMessage({
t: "setExternalApiUsages",
externalApiUsages: this.externalApiUsages,
});
if (this.isMostRecentlyActiveView(this)) {
await this.updateModelDetailsPanelState(
await this.updateMethodsUsagePanelState(
this.externalApiUsages,
this.databaseItem,
this.hideModeledApis,
@@ -389,7 +360,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
});
try {
await generateFlowModel({
await runFlowModelQueries({
cliServer: this.cliServer,
queryRunner: this.queryRunner,
queryStorageDir: this.queryStorageDir,
@@ -456,7 +427,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
return;
}
const view = new DataExtensionsEditorView(
const view = new ModelEditorView(
this.ctx,
this.app,
this.databaseManager,
@@ -467,8 +438,8 @@ export class DataExtensionsEditorView extends AbstractWebview<
addedDatabase,
modelFile,
Mode.Framework,
this.updateModelDetailsPanelState,
this.revealItemInDetailsPanel,
this.updateMethodsUsagePanelState,
this.revealItemInUsagePanel,
this.handleViewBecameActive,
this.handleViewWasDisposed,
this.isMostRecentlyActiveView,

View File

@@ -147,7 +147,12 @@ class CallableMethod extends Callable {
/** Holds if this API is a known neutral. */
pragma[nomagic]
predicate isNeutral() { this = any(FlowSummaryImpl::Public::NeutralCallable nsc).asCallable() }
predicate isNeutral() {
exists(string namespace, string type, string name, string signature, string kind, string provenance |
neutralModel(namespace, type, name, signature, kind, provenance) and
this = interpretElement(namespace, type, false, name, signature, "")
)
}
/**
* Holds if this API is supported by existing CodeQL libraries, that is, it is either a

View File

@@ -1,9 +1,8 @@
import { ExtensionPack } from "./extension-pack";
import { Mode } from "./mode";
export interface DataExtensionEditorViewState {
export interface ModelEditorViewState {
extensionPack: ExtensionPack;
enableFrameworkMode: boolean;
showLlmButton: boolean;
mode: Mode;
}

View File

@@ -55,6 +55,7 @@ function mapVariantAnalysisDtoToDto(
filePath: variantAnalysis.query.filePath,
language: mapQueryLanguageToDto(variantAnalysis.query.language),
text: variantAnalysis.query.text,
kind: variantAnalysis.query.kind,
},
databases: {
repositories: variantAnalysis.databases.repositories,

View File

@@ -55,6 +55,7 @@ function mapVariantAnalysisToDomainModel(
filePath: variantAnalysis.query.filePath,
language: mapQueryLanguageToDomainModel(variantAnalysis.query.language),
text: variantAnalysis.query.text,
kind: variantAnalysis.query.kind,
},
databases: {
repositories: variantAnalysis.databases.repositories,

View File

@@ -23,6 +23,7 @@ export interface VariantAnalysisDto {
filePath: string;
language: QueryLanguageDto;
text: string;
kind?: string;
};
databases: {
repositories?: string[];

View File

@@ -0,0 +1,22 @@
import * as React from "react";
import { Meta, StoryFn } from "@storybook/react";
import { MethodModeling as MethodModelingComponent } from "../../view/method-modeling/MethodModeling";
export default {
title: "Method Modeling/Method Modeling",
component: MethodModelingComponent,
} as Meta<typeof MethodModelingComponent>;
const Template: StoryFn<typeof MethodModelingComponent> = (args) => (
<MethodModelingComponent {...args} />
);
export const MethodUnmodeled = Template.bind({});
MethodUnmodeled.args = { modelingStatus: "unmodeled" };
export const MethodModeled = Template.bind({});
MethodModeled.args = { modelingStatus: "unsaved" };
export const MethodSaved = Template.bind({});
MethodSaved.args = { modelingStatus: "saved" };

View File

@@ -0,0 +1,18 @@
import * as React from "react";
import { Meta, StoryFn } from "@storybook/react";
import { ExternalApiUsageName as ExternalApiUsageNameComponent } from "../../view/model-editor/ExternalApiUsageName";
import { createExternalApiUsage } from "../../../test/factories/data-extension/external-api-factories";
export default {
title: "CodeQL Model Editor/External API Usage Name",
component: ExternalApiUsageNameComponent,
} as Meta<typeof ExternalApiUsageNameComponent>;
const Template: StoryFn<typeof ExternalApiUsageNameComponent> = (args) => (
<ExternalApiUsageNameComponent {...args} />
);
export const ExternalApiUsageName = Template.bind({});
ExternalApiUsageName.args = createExternalApiUsage();

View File

@@ -2,11 +2,11 @@ import * as React from "react";
import { Meta, StoryFn } from "@storybook/react";
import { MethodRow as MethodRowComponent } from "../../view/data-extensions-editor/MethodRow";
import { CallClassification } from "../../data-extensions-editor/external-api-usage";
import { MethodRow as MethodRowComponent } from "../../view/model-editor/MethodRow";
import { CallClassification } from "../../model-editor/external-api-usage";
export default {
title: "Data Extensions Editor/Method Row",
title: "CodeQL Model Editor/Method Row",
component: MethodRowComponent,
} as Meta<typeof MethodRowComponent>;

View File

@@ -2,21 +2,21 @@ import * as React from "react";
import { Meta, StoryFn } from "@storybook/react";
import { Mode } from "../../data-extensions-editor/shared/mode";
import { DataExtensionsEditor as DataExtensionsEditorComponent } from "../../view/data-extensions-editor/DataExtensionsEditor";
import { CallClassification } from "../../data-extensions-editor/external-api-usage";
import { Mode } from "../../model-editor/shared/mode";
import { ModelEditor as ModelEditorComponent } from "../../view/model-editor/ModelEditor";
import { CallClassification } from "../../model-editor/external-api-usage";
export default {
title: "Data Extensions Editor/Data Extensions Editor",
component: DataExtensionsEditorComponent,
} as Meta<typeof DataExtensionsEditorComponent>;
title: "CodeQL Model Editor/CodeQL Model Editor",
component: ModelEditorComponent,
} as Meta<typeof ModelEditorComponent>;
const Template: StoryFn<typeof DataExtensionsEditorComponent> = (args) => (
<DataExtensionsEditorComponent {...args} />
const Template: StoryFn<typeof ModelEditorComponent> = (args) => (
<ModelEditorComponent {...args} />
);
export const DataExtensionsEditor = Template.bind({});
DataExtensionsEditor.args = {
export const ModelEditor = Template.bind({});
ModelEditor.args = {
initialViewState: {
extensionPack: {
path: "/home/user/vscode-codeql-starter/codeql-custom-queries-java/sql2o",
@@ -28,7 +28,6 @@ DataExtensionsEditor.args = {
extensionTargets: {},
dataExtensions: [],
},
enableFrameworkMode: true,
showLlmButton: true,
mode: Mode.Application,
},

View File

@@ -0,0 +1,27 @@
import * as React from "react";
import { useState } from "react";
import { Meta } from "@storybook/react";
import { RepositoriesResultFormat as RepositoriesResultFormatComponent } from "../../view/variant-analysis/RepositoriesResultFormat";
import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format";
export default {
title: "Variant Analysis/Repositories Result Format",
component: RepositoriesResultFormatComponent,
argTypes: {
value: {
control: {
disable: true,
},
},
},
} as Meta<typeof RepositoriesResultFormatComponent>;
export const RepositoriesResultFormat = () => {
const [value, setValue] = useState(ResultFormat.Alerts);
return (
<RepositoriesResultFormatComponent value={value} onChange={setValue} />
);
};

View File

@@ -5,6 +5,7 @@ import { Meta } from "@storybook/react";
import { RepositoriesSearchSortRow as RepositoriesSearchSortRowComponent } from "../../view/variant-analysis/RepositoriesSearchSortRow";
import { defaultFilterSortState } from "../../variant-analysis/shared/variant-analysis-filter-sort";
import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format";
export default {
title: "Variant Analysis/Repositories Search and Sort Row",
@@ -19,9 +20,23 @@ export default {
} as Meta<typeof RepositoriesSearchSortRowComponent>;
export const RepositoriesSearchSortRow = () => {
const [value, setValue] = useState(defaultFilterSortState);
const [filterSortValue, setFilterSortValue] = useState(
defaultFilterSortState,
);
const [resultFormatValue, setResultFormatValue] = useState(
ResultFormat.Alerts,
);
const variantAnalysisQueryKind = "problem";
return (
<RepositoriesSearchSortRowComponent value={value} onChange={setValue} />
<RepositoriesSearchSortRowComponent
filterSortValue={filterSortValue}
resultFormatValue={resultFormatValue}
onFilterSortChange={setFilterSortValue}
onResultFormatChange={setResultFormatValue}
variantAnalysisQueryKind={variantAnalysisQueryKind}
/>
);
};

View File

@@ -0,0 +1,4 @@
export enum ResultFormat {
Alerts = "Alerts",
RawResults = "Raw results",
}

View File

@@ -10,6 +10,7 @@ export interface VariantAnalysis {
filePath: string;
language: QueryLanguage;
text: string;
kind?: string;
};
databases: {
repositories?: string[];
@@ -138,6 +139,7 @@ export interface VariantAnalysisSubmission {
filePath: string;
language: QueryLanguage;
text: string;
kind?: string;
// Base64 encoded query pack.
pack: string;

View File

@@ -245,6 +245,7 @@ export class VariantAnalysisManager
pack: base64Pack,
language: variantAnalysisLanguage,
text: queryText,
kind: queryMetadata?.kind,
},
databases: {
repositories: repoSelection.repositories,

View File

@@ -32,6 +32,7 @@ export function processVariantAnalysis(
filePath: submission.query.filePath,
language: submission.query.language,
text: submission.query.text,
kind: submission.query.kind,
},
databases: submission.databases,
executionStartTime: submission.startTime,

View File

@@ -199,34 +199,35 @@ export class VariantAnalysisResultsManager extends DisposableObject {
);
const sarifPath = join(resultsDirectory, "results.sarif");
const bqrsPath = join(resultsDirectory, "results.bqrs");
let interpretedResults: AnalysisAlert[] | undefined;
let rawResults: AnalysisRawResults | undefined;
if (await pathExists(sarifPath)) {
const interpretedResults = await this.readSarifResults(
interpretedResults = await this.readSarifResults(
sarifPath,
fileLinkPrefix,
);
return {
variantAnalysisId,
repositoryId: repoTask.repository.id,
interpretedResults,
};
}
if (await pathExists(bqrsPath)) {
const rawResults = await this.readBqrsResults(
rawResults = await this.readBqrsResults(
bqrsPath,
fileLinkPrefix,
repoTask.sourceLocationPrefix,
);
return {
variantAnalysisId,
repositoryId: repoTask.repository.id,
rawResults,
};
}
throw new Error("Missing results file");
if (!interpretedResults && !rawResults) {
throw new Error("Missing results file");
}
return {
variantAnalysisId,
repositoryId: repoTask.repository.id,
interpretedResults,
rawResults,
};
}
public async isVariantAnalysisRepoDownloaded(

View File

@@ -0,0 +1,44 @@
import * as React from "react";
import { styled } from "styled-components";
import {
ModelingStatus,
ModelingStatusIndicator,
} from "../model-editor/ModelingStatusIndicator";
import { ExternalApiUsage } from "../../model-editor/external-api-usage";
import { ExternalApiUsageName } from "../model-editor/ExternalApiUsageName";
const Container = styled.div`
background-color: var(--vscode-peekViewResult-background);
padding: 0.3rem;
margin-bottom: 1rem;
`;
const Title = styled.div`
padding-bottom: 0.3rem;
font-size: 1.2em;
`;
const DependencyContainer = styled.div`
display: flex;
justify-content: space-between;
`;
export type MethodModelingProps = {
modelingStatus: ModelingStatus;
externalApiUsage: ExternalApiUsage;
};
export const MethodModeling = ({
modelingStatus,
externalApiUsage,
}: MethodModelingProps): JSX.Element => {
return (
<Container>
<Title>API or Method</Title>
<DependencyContainer>
<ExternalApiUsageName {...externalApiUsage} />
<ModelingStatusIndicator status={modelingStatus} />
</DependencyContainer>
</Container>
);
};

View File

@@ -0,0 +1,44 @@
import * as React from "react";
import { useEffect } from "react";
import { MethodModeling } from "./MethodModeling";
import { ModelingStatus } from "../model-editor/ModelingStatusIndicator";
import { ExternalApiUsage } from "../../model-editor/external-api-usage";
export function MethodModelingView(): JSX.Element {
useEffect(() => {
const listener = (evt: MessageEvent) => {
if (evt.origin === window.origin) {
// Nothing to do yet.
} else {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
console.error(`Invalid event origin ${origin}`);
}
};
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
}, []);
const modelingStatus: ModelingStatus = "saved";
const externalApiUsage: ExternalApiUsage = {
library: "sql2o",
libraryVersion: "1.6.0",
signature: "org.sql2o.Connection#createQuery(String)",
packageName: "org.sql2o",
typeName: "Connection",
methodName: "createQuery",
methodParameters: "(String)",
supported: true,
supportedType: "summary",
usages: [],
};
return (
<MethodModeling
modelingStatus={modelingStatus}
externalApiUsage={externalApiUsage}
/>
);
}

View File

@@ -0,0 +1,18 @@
import * as React from "react";
import { render as reactRender, screen } from "@testing-library/react";
import { MethodModeling, MethodModelingProps } from "../MethodModeling";
import { createExternalApiUsage } from "../../../../test/factories/data-extension/external-api-factories";
describe(MethodModeling.name, () => {
const render = (props: MethodModelingProps) =>
reactRender(<MethodModeling {...props} />);
it("renders method modeling panel", () => {
render({
modelingStatus: "saved",
externalApiUsage: createExternalApiUsage(),
});
expect(screen.getByText("API or Method")).toBeInTheDocument();
});
});

View File

@@ -1,9 +1,9 @@
import * as React from "react";
import { WebviewDefinition } from "../webview-definition";
import { DataExtensionsEditor } from "./DataExtensionsEditor";
import { MethodModelingView } from "./MethodModelingView";
const definition: WebviewDefinition = {
component: <DataExtensionsEditor />,
component: <MethodModelingView />,
};
export default definition;

View File

@@ -0,0 +1,19 @@
import * as React from "react";
import { styled } from "styled-components";
import { ExternalApiUsage } from "../../model-editor/external-api-usage";
const Name = styled.span`
font-family: var(--vscode-editor-font-family);
`;
export const ExternalApiUsageName = (
externalApiUsage: ExternalApiUsage,
): JSX.Element => {
return (
<Name>
{externalApiUsage.packageName && <>{externalApiUsage.packageName}.</>}
{externalApiUsage.typeName}.{externalApiUsage.methodName}
{externalApiUsage.methodParameters}
</Name>
);
};

View File

@@ -1,6 +1,6 @@
import * as React from "react";
import { ChangeEvent, useCallback, useEffect, useMemo } from "react";
import type { ModeledMethod } from "../../data-extensions-editor/modeled-method";
import type { ModeledMethod } from "../../model-editor/modeled-method";
import { Dropdown } from "../common/Dropdown";
type Props = {

View File

@@ -1,20 +1,20 @@
import * as React from "react";
import { useCallback, useMemo, useState } from "react";
import { styled } from "styled-components";
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
import { ModeledMethod } from "../../data-extensions-editor/modeled-method";
import { ExternalApiUsage } from "../../model-editor/external-api-usage";
import { ModeledMethod } from "../../model-editor/modeled-method";
import { ModeledMethodDataGrid } from "./ModeledMethodDataGrid";
import { calculateModeledPercentage } from "../../data-extensions-editor/shared/modeled-percentage";
import { calculateModeledPercentage } from "../../model-editor/shared/modeled-percentage";
import { percentFormatter } from "./formatters";
import { Codicon } from "../common";
import { Mode } from "../../data-extensions-editor/shared/mode";
import { Mode } from "../../model-editor/shared/mode";
import {
VSCodeButton,
VSCodeDivider,
VSCodeTag,
} from "@vscode/webview-ui-toolkit/react";
import { DataExtensionEditorViewState } from "../../data-extensions-editor/shared/view-state";
import { InProgressMethods } from "../../data-extensions-editor/shared/in-progress-methods";
import { ModelEditorViewState } from "../../model-editor/shared/view-state";
import { InProgressMethods } from "../../model-editor/shared/in-progress-methods";
const LibraryContainer = styled.div`
background-color: var(--vscode-peekViewResult-background);
@@ -74,7 +74,7 @@ type Props = {
modeledMethods: Record<string, ModeledMethod>;
modifiedSignatures: Set<string>;
inProgressMethods: InProgressMethods;
viewState: DataExtensionEditorViewState;
viewState: ModelEditorViewState;
hideModeledApis: boolean;
onChange: (
modelName: string,
@@ -221,13 +221,12 @@ export const LibraryRow = ({
&nbsp;Model from source
</VSCodeButton>
)}
{viewState.enableFrameworkMode &&
viewState.mode === Mode.Application && (
<VSCodeButton appearance="icon" onClick={handleModelDependency}>
<Codicon name="references" label="Model dependency" />
&nbsp;Model dependency
</VSCodeButton>
)}
{viewState.mode === Mode.Application && (
<VSCodeButton appearance="icon" onClick={handleModelDependency}>
<Codicon name="references" label="Model dependency" />
&nbsp;Model dependency
</VSCodeButton>
)}
</TitleContainer>
{isExpanded && (
<>

View File

@@ -3,7 +3,7 @@ import { useMemo } from "react";
import {
CallClassification,
ExternalApiUsage,
} from "../../data-extensions-editor/external-api-usage";
} from "../../model-editor/external-api-usage";
import { VSCodeTag } from "@vscode/webview-ui-toolkit/react";
import { styled } from "styled-components";

View File

@@ -9,15 +9,15 @@ import { ChangeEvent, useCallback, useMemo } from "react";
import { styled } from "styled-components";
import { vscode } from "../vscode-api";
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
import { ExternalApiUsage } from "../../model-editor/external-api-usage";
import {
ModeledMethod,
ModeledMethodType,
Provenance,
} from "../../data-extensions-editor/modeled-method";
} from "../../model-editor/modeled-method";
import { KindInput } from "./KindInput";
import { extensiblePredicateDefinitions } from "../../data-extensions-editor/predicates";
import { Mode } from "../../data-extensions-editor/shared/mode";
import { extensiblePredicateDefinitions } from "../../model-editor/predicates";
import { Mode } from "../../model-editor/shared/mode";
import { Dropdown } from "../common/Dropdown";
import { MethodClassifications } from "./MethodClassifications";
import {
@@ -25,6 +25,7 @@ import {
ModelingStatusIndicator,
} from "./ModelingStatusIndicator";
import { InProgressDropdown } from "./InProgressDropdown";
import { ExternalApiUsageName } from "./ExternalApiUsageName";
const ApiOrMethodCell = styled(VSCodeDataGridCell)`
display: flex;
@@ -218,7 +219,7 @@ function ModelableMethodRow(props: Props) {
<ApiOrMethodCell gridColumn={1}>
<ModelingStatusIndicator status={modelingStatus} />
<MethodClassifications externalApiUsage={externalApiUsage} />
<ExternalApiUsageName {...props} />
<ExternalApiUsageName {...props.externalApiUsage} />
{mode === Mode.Application && (
<UsagesButton onClick={jumpToUsage}>
{externalApiUsage.usages.length}
@@ -294,7 +295,7 @@ function UnmodelableMethodRow(props: Props) {
<VSCodeDataGridRow>
<ApiOrMethodCell gridColumn={1}>
<ModelingStatusIndicator status="saved" />
<ExternalApiUsageName {...props} />
<ExternalApiUsageName {...props.externalApiUsage} />
{mode === Mode.Application && (
<UsagesButton onClick={jumpToUsage}>
{externalApiUsage.usages.length}
@@ -310,18 +311,6 @@ function UnmodelableMethodRow(props: Props) {
);
}
function ExternalApiUsageName(props: { externalApiUsage: ExternalApiUsage }) {
return (
<span>
{props.externalApiUsage.packageName && (
<>{props.externalApiUsage.packageName}.</>
)}
{props.externalApiUsage.typeName}.{props.externalApiUsage.methodName}
{props.externalApiUsage.methodParameters}
</span>
);
}
function sendJumpToUsageMessage(externalApiUsage: ExternalApiUsage) {
vscode.postMessage({
t: "jumpToUsage",

View File

@@ -1,25 +1,25 @@
import * as React from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { ToDataExtensionsEditorMessage } from "../../common/interface-types";
import { ToModelEditorMessage } from "../../common/interface-types";
import {
VSCodeButton,
VSCodeCheckbox,
VSCodeTag,
} from "@vscode/webview-ui-toolkit/react";
import { styled } from "styled-components";
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
import { ModeledMethod } from "../../data-extensions-editor/modeled-method";
import { ExternalApiUsage } from "../../model-editor/external-api-usage";
import { ModeledMethod } from "../../model-editor/modeled-method";
import { assertNever } from "../../common/helpers-pure";
import { vscode } from "../vscode-api";
import { calculateModeledPercentage } from "../../data-extensions-editor/shared/modeled-percentage";
import { calculateModeledPercentage } from "../../model-editor/shared/modeled-percentage";
import { LinkIconButton } from "../variant-analysis/LinkIconButton";
import { DataExtensionEditorViewState } from "../../data-extensions-editor/shared/view-state";
import { ModelEditorViewState } from "../../model-editor/shared/view-state";
import { ModeledMethodsList } from "./ModeledMethodsList";
import { percentFormatter } from "./formatters";
import { Mode } from "../../data-extensions-editor/shared/mode";
import { InProgressMethods } from "../../data-extensions-editor/shared/in-progress-methods";
import { Mode } from "../../model-editor/shared/mode";
import { InProgressMethods } from "../../model-editor/shared/in-progress-methods";
import { getLanguageDisplayName } from "../../common/query-language";
import { INITIAL_HIDE_MODELED_APIS_VALUE } from "../../data-extensions-editor/shared/hide-modeled-apis";
import { INITIAL_HIDE_MODELED_APIS_VALUE } from "../../model-editor/shared/hide-modeled-apis";
const LoadingContainer = styled.div`
text-align: center;
@@ -28,7 +28,7 @@ const LoadingContainer = styled.div`
font-weight: 600;
`;
const DataExtensionsEditorContainer = styled.div`
const ModelEditorContainer = styled.div`
margin-top: 1rem;
`;
@@ -72,21 +72,21 @@ const ButtonsContainer = styled.div`
`;
type Props = {
initialViewState?: DataExtensionEditorViewState;
initialViewState?: ModelEditorViewState;
initialExternalApiUsages?: ExternalApiUsage[];
initialModeledMethods?: Record<string, ModeledMethod>;
initialHideModeledApis?: boolean;
};
export function DataExtensionsEditor({
export function ModelEditor({
initialViewState,
initialExternalApiUsages = [],
initialModeledMethods = {},
initialHideModeledApis = INITIAL_HIDE_MODELED_APIS_VALUE,
}: Props): JSX.Element {
const [viewState, setViewState] = useState<
DataExtensionEditorViewState | undefined
>(initialViewState);
const [viewState, setViewState] = useState<ModelEditorViewState | undefined>(
initialViewState,
);
const [externalApiUsages, setExternalApiUsages] = useState<
ExternalApiUsage[]
@@ -117,9 +117,9 @@ export function DataExtensionsEditor({
useEffect(() => {
const listener = (evt: MessageEvent) => {
if (evt.origin === window.origin) {
const msg: ToDataExtensionsEditorMessage = evt.data;
const msg: ToModelEditorMessage = evt.data;
switch (msg.t) {
case "setDataExtensionEditorViewState":
case "setModelEditorViewState":
setViewState(msg.viewState);
break;
case "setExternalApiUsages":
@@ -297,7 +297,7 @@ export function DataExtensionsEditor({
}
return (
<DataExtensionsEditorContainer>
<ModelEditorContainer>
<HeaderContainer>
<HeaderColumn>
<HeaderRow>
@@ -320,14 +320,12 @@ export function DataExtensionsEditor({
<span slot="start" className="codicon codicon-package"></span>
Open extension pack
</LinkIconButton>
{viewState.enableFrameworkMode && (
<LinkIconButton onClick={onSwitchModeClick}>
<span slot="start" className="codicon codicon-library"></span>
{viewState.mode === Mode.Framework
? "Model as application"
: "Model as dependency"}
</LinkIconButton>
)}
<LinkIconButton onClick={onSwitchModeClick}>
<span slot="start" className="codicon codicon-library"></span>
{viewState.mode === Mode.Framework
? "Model as application"
: "Model as dependency"}
</LinkIconButton>
</HeaderRow>
</HeaderColumn>
<HeaderSpacer />
@@ -349,11 +347,9 @@ export function DataExtensionsEditor({
>
Save all
</VSCodeButton>
{viewState.enableFrameworkMode && (
<VSCodeButton appearance="secondary" onClick={onRefreshClick}>
Refresh
</VSCodeButton>
)}
<VSCodeButton appearance="secondary" onClick={onRefreshClick}>
Refresh
</VSCodeButton>
{viewState.mode === Mode.Framework && (
<VSCodeButton onClick={onGenerateFromSourceClick}>
Generate
@@ -375,6 +371,6 @@ export function DataExtensionsEditor({
onModelDependencyClick={onModelDependencyClick}
/>
</EditorContainer>
</DataExtensionsEditorContainer>
</ModelEditorContainer>
);
}

View File

@@ -5,12 +5,12 @@ import {
VSCodeDataGridRow,
} from "@vscode/webview-ui-toolkit/react";
import { MethodRow } from "./MethodRow";
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
import { ModeledMethod } from "../../data-extensions-editor/modeled-method";
import { ExternalApiUsage } from "../../model-editor/external-api-usage";
import { ModeledMethod } from "../../model-editor/modeled-method";
import { useMemo } from "react";
import { Mode } from "../../data-extensions-editor/shared/mode";
import { sortMethods } from "../../data-extensions-editor/shared/sorting";
import { InProgressMethods } from "../../data-extensions-editor/shared/in-progress-methods";
import { Mode } from "../../model-editor/shared/mode";
import { sortMethods } from "../../model-editor/shared/sorting";
import { InProgressMethods } from "../../model-editor/shared/in-progress-methods";
type Props = {
packageName: string;

View File

@@ -1,22 +1,22 @@
import * as React from "react";
import { useMemo } from "react";
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
import { ModeledMethod } from "../../data-extensions-editor/modeled-method";
import { ExternalApiUsage } from "../../model-editor/external-api-usage";
import { ModeledMethod } from "../../model-editor/modeled-method";
import { LibraryRow } from "./LibraryRow";
import { Mode } from "../../data-extensions-editor/shared/mode";
import { Mode } from "../../model-editor/shared/mode";
import {
groupMethods,
sortGroupNames,
} from "../../data-extensions-editor/shared/sorting";
import { DataExtensionEditorViewState } from "../../data-extensions-editor/shared/view-state";
import { InProgressMethods } from "../../data-extensions-editor/shared/in-progress-methods";
} from "../../model-editor/shared/sorting";
import { ModelEditorViewState } from "../../model-editor/shared/view-state";
import { InProgressMethods } from "../../model-editor/shared/in-progress-methods";
type Props = {
externalApiUsages: ExternalApiUsage[];
modeledMethods: Record<string, ModeledMethod>;
modifiedSignatures: Set<string>;
inProgressMethods: InProgressMethods;
viewState: DataExtensionEditorViewState;
viewState: ModelEditorViewState;
hideModeledApis: boolean;
onChange: (
modelName: string,

View File

@@ -0,0 +1,18 @@
import * as React from "react";
import { render as reactRender, screen } from "@testing-library/react";
import { ExternalApiUsageName } from "../ExternalApiUsageName";
import { ExternalApiUsage } from "../../../model-editor/external-api-usage";
import { createExternalApiUsage } from "../../../../test/factories/data-extension/external-api-factories";
describe(ExternalApiUsageName.name, () => {
const render = (props: ExternalApiUsage) =>
reactRender(<ExternalApiUsageName {...props} />);
it("renders method name", () => {
const apiUsage = createExternalApiUsage();
render(apiUsage);
const name = `${apiUsage.packageName}.${apiUsage.typeName}.${apiUsage.methodName}${apiUsage.methodParameters}`;
expect(screen.getByText(name)).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,9 @@
import * as React from "react";
import { WebviewDefinition } from "../webview-definition";
import { ModelEditor } from "./ModelEditor";
const definition: WebviewDefinition = {
component: <ModelEditor />,
};
export default definition;

View File

@@ -11,6 +11,7 @@ import {
VariantAnalysisScannedRepositoryDownloadStatus,
} from "../../variant-analysis/shared/variant-analysis";
import { Alert } from "../common";
import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format";
const ContentContainer = styled.div`
display: flex;
@@ -37,12 +38,28 @@ const RawResultsContainer = styled.div`
margin-top: 0.5em;
`;
function chooseResultFormat(
interpretedResults: AnalysisAlert[] | undefined,
rawResults: AnalysisRawResults | undefined,
resultFormat: ResultFormat,
): ResultFormat | undefined {
if (interpretedResults && resultFormat === ResultFormat.Alerts) {
return ResultFormat.Alerts;
} else if (rawResults) {
return ResultFormat.RawResults;
} else {
return undefined;
}
}
export type AnalyzedRepoItemContentProps = {
status?: VariantAnalysisRepoStatus;
downloadStatus?: VariantAnalysisScannedRepositoryDownloadStatus;
interpretedResults?: AnalysisAlert[];
rawResults?: AnalysisRawResults;
resultFormat: ResultFormat;
};
export const AnalyzedRepoItemContent = ({
@@ -50,7 +67,13 @@ export const AnalyzedRepoItemContent = ({
downloadStatus,
interpretedResults,
rawResults,
resultFormat,
}: AnalyzedRepoItemContentProps) => {
const chosenResultFormat = chooseResultFormat(
interpretedResults,
rawResults,
resultFormat,
);
return (
<ContentContainer>
{status === VariantAnalysisRepoStatus.Failed && (
@@ -90,7 +113,7 @@ export const AnalyzedRepoItemContent = ({
/>
</AlertContainer>
)}
{interpretedResults && (
{interpretedResults && chosenResultFormat === ResultFormat.Alerts && (
<InterpretedResultsContainer>
{interpretedResults.map((r, i) => (
<InterpretedResultItem key={i}>
@@ -99,7 +122,7 @@ export const AnalyzedRepoItemContent = ({
))}
</InterpretedResultsContainer>
)}
{rawResults && (
{rawResults && chosenResultFormat === ResultFormat.RawResults && (
<RawResultsContainer>
<RawResultsTable
schema={rawResults.schema}

View File

@@ -26,6 +26,7 @@ import { AnalyzedRepoItemContent } from "./AnalyzedRepoItemContent";
import StarCount from "../common/StarCount";
import { useTelemetryOnChange } from "../common/telemetry";
import { DeterminateProgressRing } from "../common/DeterminateProgressRing";
import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format";
// This will ensure that these icons have a className which we can use in the TitleContainer
const ExpandCollapseCodicon = styled(Codicon)``;
@@ -98,6 +99,8 @@ export type RepoRowProps = {
interpretedResults?: AnalysisAlert[];
rawResults?: AnalysisRawResults;
resultFormat?: ResultFormat;
selected?: boolean;
onSelectedChange?: (repositoryId: number, selected: boolean) => void;
};
@@ -168,6 +171,7 @@ export const RepoRow = ({
resultCount,
interpretedResults,
rawResults,
resultFormat = ResultFormat.Alerts,
selected,
onSelectedChange,
}: RepoRowProps) => {
@@ -304,6 +308,7 @@ export const RepoRow = ({
downloadStatus={downloadState?.downloadStatus}
interpretedResults={interpretedResults}
rawResults={rawResults}
resultFormat={resultFormat}
/>
)}
</div>

View File

@@ -0,0 +1,44 @@
import * as React from "react";
import { useCallback } from "react";
import { styled } from "styled-components";
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react";
import { Codicon } from "../common";
import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format";
const Dropdown = styled(VSCodeDropdown)`
width: 100%;
`;
type Props = {
value: ResultFormat;
onChange: (value: ResultFormat) => void;
className?: string;
};
export const RepositoriesResultFormat = ({
value,
onChange,
className,
}: Props) => {
const handleInput = useCallback(
(e: InputEvent) => {
const target = e.target as HTMLSelectElement;
onChange(target.value as ResultFormat);
},
[onChange],
);
return (
<Dropdown value={value} onInput={handleInput} className={className}>
<Codicon name="table" label="Result format..." slot="indicator" />
<VSCodeOption value={ResultFormat.Alerts}>
{ResultFormat.Alerts}
</VSCodeOption>
<VSCodeOption value={ResultFormat.RawResults}>
{ResultFormat.RawResults}
</VSCodeOption>
</Dropdown>
);
};

View File

@@ -9,10 +9,15 @@ import {
import { RepositoriesSearch } from "./RepositoriesSearch";
import { RepositoriesSort } from "./RepositoriesSort";
import { RepositoriesFilter } from "./RepositoriesFilter";
import { RepositoriesResultFormat } from "./RepositoriesResultFormat";
import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format";
type Props = {
value: RepositoriesFilterSortState;
onChange: Dispatch<SetStateAction<RepositoriesFilterSortState>>;
filterSortValue: RepositoriesFilterSortState;
resultFormatValue: ResultFormat;
onFilterSortChange: Dispatch<SetStateAction<RepositoriesFilterSortState>>;
onResultFormatChange: Dispatch<SetStateAction<ResultFormat>>;
variantAnalysisQueryKind: string | undefined;
};
const Container = styled.div`
@@ -35,51 +40,83 @@ const RepositoriesSortColumn = styled(RepositoriesSort)`
flex: 1;
`;
export const RepositoriesSearchSortRow = ({ value, onChange }: Props) => {
const RepositoriesResultFormatColumn = styled(RepositoriesResultFormat)`
flex: 1;
`;
function showResultFormatColumn(
variantAnalysisQueryKind: string | undefined,
): boolean {
return (
variantAnalysisQueryKind === "problem" ||
variantAnalysisQueryKind === "path-problem"
);
}
export const RepositoriesSearchSortRow = ({
filterSortValue,
resultFormatValue,
onFilterSortChange,
onResultFormatChange,
variantAnalysisQueryKind,
}: Props) => {
const handleSearchValueChange = useCallback(
(searchValue: string) => {
onChange((oldValue) => ({
onFilterSortChange((oldValue) => ({
...oldValue,
searchValue,
}));
},
[onChange],
[onFilterSortChange],
);
const handleFilterKeyChange = useCallback(
(filterKey: FilterKey) => {
onChange((oldValue) => ({
onFilterSortChange((oldValue) => ({
...oldValue,
filterKey,
}));
},
[onChange],
[onFilterSortChange],
);
const handleSortKeyChange = useCallback(
(sortKey: SortKey) => {
onChange((oldValue) => ({
onFilterSortChange((oldValue) => ({
...oldValue,
sortKey,
}));
},
[onChange],
[onFilterSortChange],
);
const handleResultFormatChange = useCallback(
(resultFormat: ResultFormat) => {
onResultFormatChange(resultFormat);
},
[onResultFormatChange],
);
return (
<Container>
<RepositoriesSearchColumn
value={value.searchValue}
value={filterSortValue.searchValue}
onChange={handleSearchValueChange}
/>
<RepositoriesFilterColumn
value={value.filterKey}
value={filterSortValue.filterKey}
onChange={handleFilterKeyChange}
/>
<RepositoriesSortColumn
value={value.sortKey}
value={filterSortValue.sortKey}
onChange={handleSortKeyChange}
/>
{showResultFormatColumn(variantAnalysisQueryKind) && (
<RepositoriesResultFormatColumn
value={resultFormatValue}
onChange={handleResultFormatChange}
/>
)}
</Container>
);
};

View File

@@ -11,6 +11,7 @@ import {
filterAndSortRepositoriesWithResultsByName,
RepositoriesFilterSortState,
} from "../../variant-analysis/shared/variant-analysis-filter-sort";
import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format";
const Container = styled.div`
display: flex;
@@ -26,6 +27,8 @@ export type VariantAnalysisAnalyzedReposProps = {
filterSortState?: RepositoriesFilterSortState;
resultFormat: ResultFormat;
selectedRepositoryIds?: number[];
setSelectedRepositoryIds?: Dispatch<SetStateAction<number[]>>;
};
@@ -35,6 +38,7 @@ export const VariantAnalysisAnalyzedRepos = ({
repositoryStates,
repositoryResults,
filterSortState,
resultFormat,
selectedRepositoryIds,
setSelectedRepositoryIds,
}: VariantAnalysisAnalyzedReposProps) => {
@@ -93,6 +97,7 @@ export const VariantAnalysisAnalyzedRepos = ({
resultCount={repository.resultCount}
interpretedResults={results?.interpretedResults}
rawResults={results?.rawResults}
resultFormat={resultFormat}
selected={selectedRepositoryIds?.includes(repository.repository.id)}
onSelectedChange={onSelectedChange}
/>

View File

@@ -1,5 +1,5 @@
import * as React from "react";
import { Dispatch, SetStateAction } from "react";
import { Dispatch, SetStateAction, useState } from "react";
import { styled } from "styled-components";
import {
VSCodeBadge,
@@ -20,6 +20,7 @@ import { VariantAnalysisSkippedRepositoriesTab } from "./VariantAnalysisSkippedR
import { RepositoriesFilterSortState } from "../../variant-analysis/shared/variant-analysis-filter-sort";
import { RepositoriesSearchSortRow } from "./RepositoriesSearchSortRow";
import { FailureReasonAlert } from "./FailureReasonAlert";
import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format";
export type VariantAnalysisOutcomePanelProps = {
variantAnalysis: VariantAnalysis;
@@ -70,6 +71,7 @@ export const VariantAnalysisOutcomePanels = ({
const accessMismatchRepositoryCount =
variantAnalysis.skippedRepos?.accessMismatchRepos?.repositoryCount ?? 0;
const [resultFormat, setResultFormat] = useState(ResultFormat.Alerts);
const warnings = (
<WarningsContainer>
{variantAnalysis.status === VariantAnalysisStatus.Canceled && (
@@ -123,14 +125,18 @@ export const VariantAnalysisOutcomePanels = ({
<>
{warnings}
<RepositoriesSearchSortRow
value={filterSortState}
onChange={setFilterSortState}
filterSortValue={filterSortState}
resultFormatValue={resultFormat}
onFilterSortChange={setFilterSortState}
onResultFormatChange={setResultFormat}
variantAnalysisQueryKind={variantAnalysis.query.kind}
/>
<VariantAnalysisAnalyzedRepos
variantAnalysis={variantAnalysis}
repositoryStates={repositoryStates}
repositoryResults={repositoryResults}
filterSortState={filterSortState}
resultFormat={resultFormat}
selectedRepositoryIds={selectedRepositoryIds}
setSelectedRepositoryIds={setSelectedRepositoryIds}
/>
@@ -142,8 +148,11 @@ export const VariantAnalysisOutcomePanels = ({
<>
{warnings}
<RepositoriesSearchSortRow
value={filterSortState}
onChange={setFilterSortState}
filterSortValue={filterSortState}
resultFormatValue={resultFormat}
onFilterSortChange={setFilterSortState}
onResultFormatChange={setResultFormat}
variantAnalysisQueryKind={variantAnalysis.query.kind}
/>
<VSCodePanels>
{scannedReposCount > 0 && (
@@ -177,6 +186,7 @@ export const VariantAnalysisOutcomePanels = ({
repositoryStates={repositoryStates}
repositoryResults={repositoryResults}
filterSortState={filterSortState}
resultFormat={resultFormat}
selectedRepositoryIds={selectedRepositoryIds}
setSelectedRepositoryIds={setSelectedRepositoryIds}
/>

View File

@@ -8,12 +8,14 @@ import {
AnalyzedRepoItemContent,
AnalyzedRepoItemContentProps,
} from "../AnalyzedRepoItemContent";
import { ResultFormat } from "../../../variant-analysis/shared/variant-analysis-result-format";
describe(AnalyzedRepoItemContent.name, () => {
const render = (props: Partial<AnalyzedRepoItemContentProps> = {}) => {
return reactRender(
<AnalyzedRepoItemContent
status={VariantAnalysisRepoStatus.Succeeded}
resultFormat={ResultFormat.Alerts}
{...props}
/>,
);

View File

@@ -15,6 +15,7 @@ import { createMockRepositoryWithMetadata } from "../../../../test/factories/var
import { createMockScannedRepo } from "../../../../test/factories/variant-analysis/shared/scanned-repositories";
import { SortKey } from "../../../variant-analysis/shared/variant-analysis-filter-sort";
import { permissiveFilterSortState } from "../../../../test/unit-tests/variant-analysis-filter-sort.test";
import { ResultFormat } from "../../../variant-analysis/shared/variant-analysis-result-format";
describe(VariantAnalysisAnalyzedRepos.name, () => {
const defaultVariantAnalysis = createMockVariantAnalysis({
@@ -99,6 +100,7 @@ describe(VariantAnalysisAnalyzedRepos.name, () => {
return reactRender(
<VariantAnalysisAnalyzedRepos
variantAnalysis={defaultVariantAnalysis}
resultFormat={ResultFormat.Alerts}
{...props}
/>,
);

View File

@@ -1,6 +1,6 @@
import {
FromCompareViewMessage,
FromDataExtensionsEditorMessage,
FromModelEditorMessage,
FromResultsViewMsg,
FromVariantAnalysisMessage,
VariantAnalysisState,
@@ -15,7 +15,7 @@ interface VsCodeApi {
| FromResultsViewMsg
| FromCompareViewMessage
| FromVariantAnalysisMessage
| FromDataExtensionsEditorMessage,
| FromModelEditorMessage,
): void;
/**

View File

@@ -1,5 +1,5 @@
[
"v2.14.2",
"v2.14.3",
"v2.13.5",
"v2.12.7",
"v2.11.6",

View File

@@ -0,0 +1,57 @@
import {
Usage,
ExternalApiUsage,
CallClassification,
} from "../../../src/model-editor/external-api-usage";
import { ModeledMethodType } from "../../../src/model-editor/modeled-method";
import { ResolvableLocationValue } from "../../../src/common/bqrs-cli-types";
export function createExternalApiUsage({
library = "sql2o-1.6.0.jar",
supported = true,
supportedType = "summary" as ModeledMethodType,
usages = [],
signature = "org.sql2o.Sql2o#open()",
packageName = "org.sql2o",
typeName = "Sql2o",
methodName = "open",
methodParameters = "()",
}: {
library?: string;
supported?: boolean;
supportedType?: ModeledMethodType;
usages?: Usage[];
signature?: string;
packageName?: string;
typeName?: string;
methodName?: string;
methodParameters?: string;
} = {}): ExternalApiUsage {
return {
library,
supported,
supportedType,
usages,
signature,
packageName,
typeName,
methodName,
methodParameters,
};
}
export function createUsage({
classification = CallClassification.Unknown,
label = "test",
url = {} as ResolvableLocationValue,
}: {
classification?: CallClassification;
label?: string;
url?: ResolvableLocationValue;
} = {}): Usage {
return {
classification,
label,
url,
};
}

View File

@@ -12,6 +12,7 @@ export function createMockSubmission(): VariantAnalysisSubmission {
filePath: "query-file-path",
language: QueryLanguage.Javascript,
text: "query-text",
kind: "table",
pack: "base64-encoded-string",
},
databases: {

View File

@@ -45,7 +45,7 @@ describe("commands declared in package.json", () => {
command.match(/^codeQLAstViewer\./) ||
command.match(/^codeQLEvalLogViewer\./) ||
command.match(/^codeQLTests\./) ||
command.match(/^codeQLDataExtensionsEditor\./)
command.match(/^codeQLModelEditor\./)
) {
scopedCmds.add(command);
expect(title).toBeDefined();

View File

@@ -2,14 +2,14 @@ import {
createAutoModelRequest,
encodeSarif,
getCandidates,
} from "../../../src/data-extensions-editor/auto-model";
import { Mode } from "../../../src/data-extensions-editor/shared/mode";
import { AutomodelMode } from "../../../src/data-extensions-editor/auto-model-api";
import { AutoModelQueriesResult } from "../../../src/data-extensions-editor/auto-model-codeml-queries";
} from "../../../src/model-editor/auto-model";
import { Mode } from "../../../src/model-editor/shared/mode";
import { AutomodelMode } from "../../../src/model-editor/auto-model-api";
import { AutoModelQueriesResult } from "../../../src/model-editor/auto-model-codeml-queries";
import * as sarif from "sarif";
import { gzipDecode } from "../../../src/common/zlib";
import { ExternalApiUsage } from "../../../src/data-extensions-editor/external-api-usage";
import { ModeledMethod } from "../../../src/data-extensions-editor/modeled-method";
import { ExternalApiUsage } from "../../../src/model-editor/external-api-usage";
import { ModeledMethod } from "../../../src/model-editor/modeled-method";
describe("createAutoModelRequest", () => {
const createSarifLog = (queryId: string): sarif.Log => {

View File

@@ -1,6 +1,6 @@
import { decodeBqrsToExternalApiUsages } from "../../../src/data-extensions-editor/bqrs";
import { decodeBqrsToExternalApiUsages } from "../../../src/model-editor/bqrs";
import { DecodedBqrsChunk } from "../../../src/common/bqrs-cli-types";
import { CallClassification } from "../../../src/data-extensions-editor/external-api-usage";
import { CallClassification } from "../../../src/model-editor/external-api-usage";
describe("decodeBqrsToExternalApiUsages", () => {
const chunk: DecodedBqrsChunk = {

View File

@@ -3,7 +3,7 @@ import {
formatPackName,
parsePackName,
validatePackName,
} from "../../../src/data-extensions-editor/extension-pack-name";
} from "../../../src/model-editor/extension-pack-name";
describe("autoNameExtensionPack", () => {
const testCases: Array<{

View File

@@ -1,4 +1,4 @@
import { parseLibraryFilename } from "../../../src/data-extensions-editor/library";
import { parseLibraryFilename } from "../../../src/model-editor/library";
describe("parseLibraryFilename", () => {
const testCases = [

View File

@@ -1,4 +1,4 @@
import { calculateModeledPercentage } from "../../../../src/data-extensions-editor/shared/modeled-percentage";
import { calculateModeledPercentage } from "../../../../src/model-editor/shared/modeled-percentage";
describe("calculateModeledPercentage", () => {
it("when there are no external API usages", () => {

View File

@@ -4,8 +4,8 @@ import {
createDataExtensionYamlsForFrameworkMode,
createFilenameForLibrary,
loadDataExtensionYaml,
} from "../../../src/data-extensions-editor/yaml";
import { CallClassification } from "../../../src/data-extensions-editor/external-api-usage";
} from "../../../src/model-editor/yaml";
import { CallClassification } from "../../../src/model-editor/external-api-usage";
describe("createDataExtensionYaml", () => {
it("creates the correct YAML file", () => {

View File

@@ -51,6 +51,7 @@ describe(processVariantAnalysis.name, () => {
language: QueryLanguage.Javascript,
name: "query-name",
text: mockSubmission.query.text,
kind: "table",
},
databases: {
repositories: ["1", "2", "3"],

View File

@@ -6,8 +6,8 @@ import { mkdirSync, writeFileSync } from "fs";
import {
listModelFiles,
loadModeledMethods,
} from "../../../../src/data-extensions-editor/modeled-method-fs";
import { ExtensionPack } from "../../../../src/data-extensions-editor/shared/extension-pack";
} from "../../../../src/model-editor/modeled-method-fs";
import { ExtensionPack } from "../../../../src/model-editor/shared/extension-pack";
import { join } from "path";
import { extLogger } from "../../../../src/common/logging/vscode";
import { homedir } from "os";

Some files were not shown because too many files have changed in this diff Show More