Merge branch 'main' into koesie10/markdown-lint-generated-files
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
**/* @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
|
||||
|
||||
@@ -703,6 +703,10 @@
|
||||
"title": "CodeQL: Go to QL Code",
|
||||
"enablement": "codeql.hasQLSource"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.openDataExtensionsEditor",
|
||||
"title": "CodeQL: Open Data Extensions Editor"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.mockGitHubApiServer.startRecording",
|
||||
"title": "CodeQL: Mock GitHub API Server: Start Scenario Recording"
|
||||
@@ -1086,6 +1090,10 @@
|
||||
"command": "codeQL.viewCfgContextEditor",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.openDataExtensionsEditor",
|
||||
"when": "config.codeQL.canary && config.codeQL.dataExtensions.editor"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.openConfigFile",
|
||||
"when": "false"
|
||||
|
||||
@@ -69,6 +69,8 @@ export type BaseCommands = {
|
||||
|
||||
"codeQL.copyVersion": () => Promise<void>;
|
||||
"codeQL.restartQueryServer": () => Promise<void>;
|
||||
"codeQL.restartQueryServerOnConfigChange": () => Promise<void>;
|
||||
"codeQL.restartLegacyQueryServerOnConfigChange": () => Promise<void>;
|
||||
};
|
||||
|
||||
// Commands used when working with queries in the editor
|
||||
@@ -196,7 +198,10 @@ export type VariantAnalysisCommands = {
|
||||
variantAnalysisId: number,
|
||||
repositoryFullName: string,
|
||||
) => Promise<VariantAnalysisScannedRepositoryResult>;
|
||||
"codeQL.monitorVariantAnalysis": (
|
||||
"codeQL.monitorNewVariantAnalysis": (
|
||||
variantAnalysis: VariantAnalysis,
|
||||
) => Promise<void>;
|
||||
"codeQL.monitorRehydratedVariantAnalysis": (
|
||||
variantAnalysis: VariantAnalysis,
|
||||
) => Promise<void>;
|
||||
"codeQL.openVariantAnalysisLogs": (
|
||||
@@ -241,6 +246,10 @@ export type PackagingCommands = {
|
||||
"codeQL.downloadPacks": () => Promise<void>;
|
||||
};
|
||||
|
||||
export type DataExtensionsEditorCommands = {
|
||||
"codeQL.openDataExtensionsEditor": () => Promise<void>;
|
||||
};
|
||||
|
||||
export type EvalLogViewerCommands = {
|
||||
"codeQLEvalLogViewer.clear": () => Promise<void>;
|
||||
};
|
||||
@@ -273,6 +282,7 @@ export type AllExtensionCommands = BaseCommands &
|
||||
AstCfgCommands &
|
||||
AstViewerCommands &
|
||||
PackagingCommands &
|
||||
DataExtensionsEditorCommands &
|
||||
EvalLogViewerCommands &
|
||||
SummaryLanguageSupportCommands &
|
||||
Partial<TestUICommands> &
|
||||
|
||||
54
extensions/ql-vscode/src/data-extensions-editor/bqrs.ts
Normal file
54
extensions/ql-vscode/src/data-extensions-editor/bqrs.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { DecodedBqrsChunk } from "../pure/bqrs-cli-types";
|
||||
import { Call, ExternalApiUsage } from "./external-api-usage";
|
||||
|
||||
export function decodeBqrsToExternalApiUsages(
|
||||
chunk: DecodedBqrsChunk,
|
||||
): ExternalApiUsage[] {
|
||||
const methodsByApiName = new Map<string, ExternalApiUsage>();
|
||||
|
||||
chunk?.tuples.forEach((tuple) => {
|
||||
const signature = tuple[0] as string;
|
||||
const supported = tuple[1] as boolean;
|
||||
const usage = tuple[2] as Call;
|
||||
|
||||
const [packageWithType, methodDeclaration] = signature.split("#");
|
||||
|
||||
const packageName = packageWithType.substring(
|
||||
0,
|
||||
packageWithType.lastIndexOf("."),
|
||||
);
|
||||
const typeName = packageWithType.substring(
|
||||
packageWithType.lastIndexOf(".") + 1,
|
||||
);
|
||||
|
||||
const methodName = methodDeclaration.substring(
|
||||
0,
|
||||
methodDeclaration.indexOf("("),
|
||||
);
|
||||
const methodParameters = methodDeclaration.substring(
|
||||
methodDeclaration.indexOf("("),
|
||||
);
|
||||
|
||||
if (!methodsByApiName.has(signature)) {
|
||||
methodsByApiName.set(signature, {
|
||||
signature,
|
||||
packageName,
|
||||
typeName,
|
||||
methodName,
|
||||
methodParameters,
|
||||
supported,
|
||||
usages: [],
|
||||
});
|
||||
}
|
||||
|
||||
const method = methodsByApiName.get(signature)!;
|
||||
method.usages.push(usage);
|
||||
});
|
||||
|
||||
const externalApiUsages = Array.from(methodsByApiName.values());
|
||||
externalApiUsages.sort((a, b) => {
|
||||
// Sort by number of usages descending
|
||||
return b.usages.length - a.usages.length;
|
||||
});
|
||||
return externalApiUsages;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { ExtensionContext } from "vscode";
|
||||
import { DataExtensionsEditorView } from "./data-extensions-editor-view";
|
||||
import { DataExtensionsEditorCommands } from "../common/commands";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { DatabaseManager } from "../local-databases";
|
||||
import { extLogger } from "../common";
|
||||
import { ensureDir } from "fs-extra";
|
||||
import { join } from "path";
|
||||
|
||||
export class DataExtensionsEditorModule {
|
||||
private readonly queryStorageDir: string;
|
||||
|
||||
private constructor(
|
||||
private readonly ctx: ExtensionContext,
|
||||
private readonly databaseManager: DatabaseManager,
|
||||
private readonly cliServer: CodeQLCliServer,
|
||||
private readonly queryRunner: QueryRunner,
|
||||
baseQueryStorageDir: string,
|
||||
) {
|
||||
this.queryStorageDir = join(
|
||||
baseQueryStorageDir,
|
||||
"data-extensions-editor-results",
|
||||
);
|
||||
}
|
||||
|
||||
public static async initialize(
|
||||
ctx: ExtensionContext,
|
||||
databaseManager: DatabaseManager,
|
||||
cliServer: CodeQLCliServer,
|
||||
queryRunner: QueryRunner,
|
||||
queryStorageDir: string,
|
||||
): Promise<DataExtensionsEditorModule> {
|
||||
const dataExtensionsEditorModule = new DataExtensionsEditorModule(
|
||||
ctx,
|
||||
databaseManager,
|
||||
cliServer,
|
||||
queryRunner,
|
||||
queryStorageDir,
|
||||
);
|
||||
|
||||
await dataExtensionsEditorModule.initialize();
|
||||
return dataExtensionsEditorModule;
|
||||
}
|
||||
|
||||
public getCommands(): DataExtensionsEditorCommands {
|
||||
return {
|
||||
"codeQL.openDataExtensionsEditor": async () => {
|
||||
const db = this.databaseManager.currentDatabaseItem;
|
||||
if (!db) {
|
||||
void extLogger.log("No database selected");
|
||||
return;
|
||||
}
|
||||
|
||||
const view = new DataExtensionsEditorView(
|
||||
this.ctx,
|
||||
this.cliServer,
|
||||
this.queryRunner,
|
||||
this.queryStorageDir,
|
||||
db,
|
||||
);
|
||||
await view.openView();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private async initialize(): Promise<void> {
|
||||
await ensureDir(this.queryStorageDir);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
import { CancellationTokenSource, ExtensionContext, ViewColumn } from "vscode";
|
||||
import { AbstractWebview, WebviewPanelConfig } from "../abstract-webview";
|
||||
import {
|
||||
FromDataExtensionsEditorMessage,
|
||||
ToDataExtensionsEditorMessage,
|
||||
} from "../pure/interface-types";
|
||||
import { ProgressUpdate } from "../progress";
|
||||
import { extLogger, TeeLogger } from "../common";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../queryRunner";
|
||||
import { qlpackOfDatabase } from "../contextual/queryResolver";
|
||||
import { file } from "tmp-promise";
|
||||
import { writeFile } from "fs-extra";
|
||||
import { dump } from "js-yaml";
|
||||
import {
|
||||
getOnDiskWorkspaceFolders,
|
||||
showAndLogExceptionWithTelemetry,
|
||||
} from "../helpers";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { decodeBqrsToExternalApiUsages } from "./bqrs";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { asError, getErrorMessage } from "../pure/helpers-pure";
|
||||
|
||||
export class DataExtensionsEditorView extends AbstractWebview<
|
||||
ToDataExtensionsEditorMessage,
|
||||
FromDataExtensionsEditorMessage
|
||||
> {
|
||||
public constructor(
|
||||
ctx: ExtensionContext,
|
||||
private readonly cliServer: CodeQLCliServer,
|
||||
private readonly queryRunner: QueryRunner,
|
||||
private readonly queryStorageDir: string,
|
||||
private readonly databaseItem: DatabaseItem,
|
||||
) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
public async openView() {
|
||||
const panel = await this.getPanel();
|
||||
panel.reveal(undefined, true);
|
||||
|
||||
await this.waitForPanelLoaded();
|
||||
}
|
||||
|
||||
protected async getPanelConfig(): Promise<WebviewPanelConfig> {
|
||||
return {
|
||||
viewId: "data-extensions-editor",
|
||||
title: "Data Extensions Editor",
|
||||
viewColumn: ViewColumn.Active,
|
||||
preserveFocus: true,
|
||||
view: "data-extensions-editor",
|
||||
};
|
||||
}
|
||||
|
||||
protected onPanelDispose(): void {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
protected async onMessage(
|
||||
msg: FromDataExtensionsEditorMessage,
|
||||
): Promise<void> {
|
||||
switch (msg.t) {
|
||||
case "viewLoaded":
|
||||
await this.onWebViewLoaded();
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unexpected message type");
|
||||
}
|
||||
}
|
||||
|
||||
protected async onWebViewLoaded() {
|
||||
super.onWebViewLoaded();
|
||||
|
||||
await this.loadExternalApiUsages();
|
||||
}
|
||||
|
||||
protected async loadExternalApiUsages(): Promise<void> {
|
||||
try {
|
||||
const queryResult = await this.runQuery();
|
||||
if (!queryResult) {
|
||||
await this.clearProgress();
|
||||
return;
|
||||
}
|
||||
|
||||
await this.showProgress({
|
||||
message: "Loading results",
|
||||
step: 1100,
|
||||
maxStep: 1500,
|
||||
});
|
||||
|
||||
const bqrsPath = queryResult.outputDir.bqrsPath;
|
||||
|
||||
const bqrsChunk = await this.getResults(bqrsPath);
|
||||
if (!bqrsChunk) {
|
||||
await this.clearProgress();
|
||||
return;
|
||||
}
|
||||
|
||||
await this.showProgress({
|
||||
message: "Finalizing results",
|
||||
step: 1450,
|
||||
maxStep: 1500,
|
||||
});
|
||||
|
||||
const externalApiUsages = decodeBqrsToExternalApiUsages(bqrsChunk);
|
||||
|
||||
await this.postMessage({
|
||||
t: "setExternalApiUsages",
|
||||
externalApiUsages,
|
||||
});
|
||||
|
||||
await this.clearProgress();
|
||||
} catch (err) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
redactableError(
|
||||
asError(err),
|
||||
)`Failed to load external APi usages: ${getErrorMessage(err)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async runQuery(): Promise<CoreCompletedQuery | undefined> {
|
||||
const qlpacks = await qlpackOfDatabase(this.cliServer, this.databaseItem);
|
||||
|
||||
const packsToSearch = [qlpacks.dbschemePack];
|
||||
if (qlpacks.queryPack) {
|
||||
packsToSearch.push(qlpacks.queryPack);
|
||||
}
|
||||
|
||||
const suiteFile = (
|
||||
await file({
|
||||
postfix: ".qls",
|
||||
})
|
||||
).path;
|
||||
const suiteYaml = [];
|
||||
for (const qlpack of packsToSearch) {
|
||||
suiteYaml.push({
|
||||
from: qlpack,
|
||||
queries: ".",
|
||||
include: {
|
||||
id: `${this.databaseItem.language}/telemetry/fetch-external-apis`,
|
||||
},
|
||||
});
|
||||
}
|
||||
await writeFile(suiteFile, dump(suiteYaml), "utf8");
|
||||
|
||||
const queries = await this.cliServer.resolveQueriesInSuite(
|
||||
suiteFile,
|
||||
getOnDiskWorkspaceFolders(),
|
||||
);
|
||||
|
||||
if (queries.length !== 1) {
|
||||
void extLogger.log(`Expected exactly one query, got ${queries.length}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const query = queries[0];
|
||||
|
||||
const tokenSource = new CancellationTokenSource();
|
||||
|
||||
const queryRun = this.queryRunner.createQueryRun(
|
||||
this.databaseItem.databaseUri.fsPath,
|
||||
{ queryPath: query, quickEvalPosition: undefined },
|
||||
false,
|
||||
getOnDiskWorkspaceFolders(),
|
||||
undefined,
|
||||
this.queryStorageDir,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
|
||||
return queryRun.evaluate(
|
||||
(update) => this.showProgress(update, 1500),
|
||||
tokenSource.token,
|
||||
new TeeLogger(this.queryRunner.logger, queryRun.outputDir.logPath),
|
||||
);
|
||||
}
|
||||
|
||||
private async getResults(bqrsPath: string) {
|
||||
const bqrsInfo = await this.cliServer.bqrsInfo(bqrsPath);
|
||||
if (bqrsInfo["result-sets"].length !== 1) {
|
||||
void extLogger.log(
|
||||
`Expected exactly one result set, got ${bqrsInfo["result-sets"].length}`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const resultSet = bqrsInfo["result-sets"][0];
|
||||
|
||||
await this.showProgress({
|
||||
message: "Decoding results",
|
||||
step: 1200,
|
||||
maxStep: 1500,
|
||||
});
|
||||
|
||||
return this.cliServer.bqrsDecode(bqrsPath, resultSet.name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Progress in this class is a bit weird. Most of the progress is based on running the query.
|
||||
* Query progress is always between 0 and 1000. However, we still have some steps that need
|
||||
* to be done after the query has finished. Therefore, the maximum step is 1500. This captures
|
||||
* that there's 1000 steps of the query progress since that takes the most time, and then
|
||||
* an additional 500 steps for the rest of the work. The progress doesn't need to be 100%
|
||||
* accurate, so this is just a rough estimate.
|
||||
*/
|
||||
private async showProgress(update: ProgressUpdate, maxStep?: number) {
|
||||
await this.postMessage({
|
||||
t: "showProgress",
|
||||
step: update.step,
|
||||
maxStep: maxStep ?? update.maxStep,
|
||||
message: update.message,
|
||||
});
|
||||
}
|
||||
|
||||
private async clearProgress() {
|
||||
await this.showProgress({
|
||||
step: 0,
|
||||
maxStep: 0,
|
||||
message: "",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ResolvableLocationValue } from "../pure/bqrs-cli-types";
|
||||
|
||||
export type Call = {
|
||||
label: string;
|
||||
url: ResolvableLocationValue;
|
||||
};
|
||||
|
||||
export type ExternalApiUsage = {
|
||||
/**
|
||||
* Contains the full method signature, e.g. `org.sql2o.Connection#createQuery(String)`
|
||||
*/
|
||||
signature: string;
|
||||
packageName: string;
|
||||
typeName: string;
|
||||
methodName: string;
|
||||
methodParameters: string;
|
||||
supported: boolean;
|
||||
usages: Call[];
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
export type ModeledMethodType =
|
||||
| "none"
|
||||
| "source"
|
||||
| "sink"
|
||||
| "summary"
|
||||
| "neutral";
|
||||
|
||||
export type ModeledMethod = {
|
||||
type: ModeledMethodType;
|
||||
input: string;
|
||||
output: string;
|
||||
kind: string;
|
||||
};
|
||||
@@ -120,6 +120,7 @@ import { getAstCfgCommands } from "./ast-cfg-commands";
|
||||
import { getQueryEditorCommands } from "./query-editor";
|
||||
import { App } from "./common/app";
|
||||
import { registerCommandWithErrorHandling } from "./common/vscode/commands";
|
||||
import { DataExtensionsEditorModule } from "./data-extensions-editor/data-extensions-editor-module";
|
||||
|
||||
/**
|
||||
* extension.ts
|
||||
@@ -168,27 +169,31 @@ function getCommands(
|
||||
}
|
||||
};
|
||||
|
||||
const restartQueryServer = async () =>
|
||||
withProgress(
|
||||
async (progress: ProgressCallback, token: CancellationToken) => {
|
||||
// Restart all of the spawned servers: cli, query, and language.
|
||||
cliServer.restartCliServer();
|
||||
await Promise.all([
|
||||
queryRunner.restartQueryServer(progress, token),
|
||||
ideServer.restart(),
|
||||
]);
|
||||
void showAndLogInformationMessage("CodeQL Query Server restarted.", {
|
||||
outputLogger: queryServerLogger,
|
||||
});
|
||||
},
|
||||
{
|
||||
title: "Restarting Query Server",
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
"codeQL.openDocumentation": async () => {
|
||||
await env.openExternal(Uri.parse("https://codeql.github.com/docs/"));
|
||||
},
|
||||
"codeQL.restartQueryServer": async () =>
|
||||
withProgress(
|
||||
async (progress: ProgressCallback, token: CancellationToken) => {
|
||||
// Restart all of the spawned servers: cli, query, and language.
|
||||
cliServer.restartCliServer();
|
||||
await Promise.all([
|
||||
queryRunner.restartQueryServer(progress, token),
|
||||
ideServer.restart(),
|
||||
]);
|
||||
void showAndLogInformationMessage("CodeQL Query Server restarted.", {
|
||||
outputLogger: queryServerLogger,
|
||||
});
|
||||
},
|
||||
{
|
||||
title: "Restarting Query Server",
|
||||
},
|
||||
),
|
||||
"codeQL.restartQueryServer": restartQueryServer,
|
||||
"codeQL.restartQueryServerOnConfigChange": restartQueryServer,
|
||||
"codeQL.restartLegacyQueryServerOnConfigChange": restartQueryServer,
|
||||
"codeQL.copyVersion": async () => {
|
||||
const text = `CodeQL extension version: ${
|
||||
extension?.packageJSON.version
|
||||
@@ -860,6 +865,15 @@ async function activateWithInstalledDistribution(
|
||||
);
|
||||
ctx.subscriptions.push(localQueries);
|
||||
|
||||
const dataExtensionsEditorModule =
|
||||
await DataExtensionsEditorModule.initialize(
|
||||
ctx,
|
||||
dbm,
|
||||
cliServer,
|
||||
qs,
|
||||
tmpDir.name,
|
||||
);
|
||||
|
||||
void extLogger.log("Initializing QLTest interface.");
|
||||
const testExplorerExtension = extensions.getExtension<TestHub>(
|
||||
testExplorerExtensionId,
|
||||
@@ -922,6 +936,7 @@ async function activateWithInstalledDistribution(
|
||||
...getPackagingCommands({
|
||||
cliServer,
|
||||
}),
|
||||
...dataExtensionsEditorModule.getCommands(),
|
||||
...evalLogViewer.getCommands(),
|
||||
...summaryLanguageSupport.getCommands(),
|
||||
...testUiCommands,
|
||||
|
||||
@@ -113,7 +113,8 @@ export type WebviewView =
|
||||
| "results"
|
||||
| "compare"
|
||||
| "variant-analysis"
|
||||
| "data-flow-paths";
|
||||
| "data-flow-paths"
|
||||
| "data-extensions-editor";
|
||||
|
||||
export interface WebviewMessage {
|
||||
t: string;
|
||||
|
||||
@@ -71,7 +71,7 @@ export class QueryServerClient extends DisposableObject {
|
||||
if (config.onDidChangeConfiguration !== undefined) {
|
||||
this.push(
|
||||
config.onDidChangeConfiguration(() =>
|
||||
app.commands.execute("codeQL.restartQueryServer"),
|
||||
app.commands.execute("codeQL.restartLegacyQueryServerOnConfigChange"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import { RepositoriesFilterSortStateWithIds } from "./variant-analysis-filter-sort";
|
||||
import { ErrorLike } from "./errors";
|
||||
import { DataFlowPaths } from "../variant-analysis/shared/data-flow-paths";
|
||||
import { ExternalApiUsage } from "../data-extensions-editor/external-api-usage";
|
||||
|
||||
/**
|
||||
* This module contains types and code that are shared between
|
||||
@@ -478,3 +479,21 @@ export interface SetDataFlowPathsMessage {
|
||||
export type ToDataFlowPathsMessage = SetDataFlowPathsMessage;
|
||||
|
||||
export type FromDataFlowPathsMessage = CommonFromViewMessages;
|
||||
|
||||
export interface SetExternalApiUsagesMessage {
|
||||
t: "setExternalApiUsages";
|
||||
externalApiUsages: ExternalApiUsage[];
|
||||
}
|
||||
|
||||
export interface ShowProgressMessage {
|
||||
t: "showProgress";
|
||||
step: number;
|
||||
maxStep: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type ToDataExtensionsEditorMessage =
|
||||
| SetExternalApiUsagesMessage
|
||||
| ShowProgressMessage;
|
||||
|
||||
export type FromDataExtensionsEditorMessage = ViewLoadedMsg;
|
||||
|
||||
@@ -68,7 +68,7 @@ export class QueryServerClient extends DisposableObject {
|
||||
if (config.onDidChangeConfiguration !== undefined) {
|
||||
this.push(
|
||||
config.onDidChangeConfiguration(() =>
|
||||
app.commands.execute("codeQL.restartQueryServer"),
|
||||
app.commands.execute("codeQL.restartQueryServerOnConfigChange"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { outputJson, readJson } from "fs-extra";
|
||||
import { VariantAnalysisScannedRepositoryState } from "../shared/variant-analysis";
|
||||
|
||||
export const REPO_STATES_FILENAME = "repo_states.json";
|
||||
|
||||
export async function writeRepoStates(
|
||||
storagePath: string,
|
||||
repoStates: Record<number, VariantAnalysisScannedRepositoryState> | undefined,
|
||||
): Promise<void> {
|
||||
return await outputJson(storagePath, repoStates);
|
||||
}
|
||||
|
||||
export async function readRepoStates(
|
||||
storagePath: string,
|
||||
): Promise<Record<number, VariantAnalysisScannedRepositoryState>> {
|
||||
return await readJson(storagePath);
|
||||
}
|
||||
@@ -47,7 +47,7 @@ import {
|
||||
showAndLogInformationMessage,
|
||||
showAndLogWarningMessage,
|
||||
} from "../helpers";
|
||||
import { readFile, readJson, remove, pathExists, outputJson } from "fs-extra";
|
||||
import { readFile, remove, pathExists } from "fs-extra";
|
||||
import { EOL } from "os";
|
||||
import { cancelVariantAnalysis } from "./gh-api/gh-actions-api-client";
|
||||
import {
|
||||
@@ -67,12 +67,16 @@ import { App } from "../common/app";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { AppCommandManager, VariantAnalysisCommands } from "../common/commands";
|
||||
import { exportVariantAnalysisResults } from "./export-results";
|
||||
import {
|
||||
readRepoStates,
|
||||
REPO_STATES_FILENAME,
|
||||
writeRepoStates,
|
||||
} from "./store/repo-states-store";
|
||||
|
||||
export class VariantAnalysisManager
|
||||
extends DisposableObject
|
||||
implements VariantAnalysisViewManager<VariantAnalysisView>
|
||||
{
|
||||
private static readonly REPO_STATES_FILENAME = "repo_states.json";
|
||||
private static readonly DOWNLOAD_PERCENTAGE_UPDATE_DELAY_MS = 500;
|
||||
|
||||
private readonly _onVariantAnalysisAdded = this.push(
|
||||
@@ -136,7 +140,10 @@ export class VariantAnalysisManager
|
||||
"codeQL.copyVariantAnalysisRepoList":
|
||||
this.copyRepoListToClipboard.bind(this),
|
||||
"codeQL.loadVariantAnalysisRepoResults": this.loadResults.bind(this),
|
||||
"codeQL.monitorVariantAnalysis": this.monitorVariantAnalysis.bind(this),
|
||||
"codeQL.monitorNewVariantAnalysis":
|
||||
this.monitorVariantAnalysis.bind(this),
|
||||
"codeQL.monitorRehydratedVariantAnalysis":
|
||||
this.monitorVariantAnalysis.bind(this),
|
||||
"codeQL.openVariantAnalysisLogs": this.openVariantAnalysisLogs.bind(this),
|
||||
"codeQL.openVariantAnalysisView": this.showView.bind(this),
|
||||
"codeQL.runVariantAnalysis":
|
||||
@@ -244,7 +251,7 @@ export class VariantAnalysisManager
|
||||
processedVariantAnalysis.id,
|
||||
);
|
||||
void this.app.commands.execute(
|
||||
"codeQL.monitorVariantAnalysis",
|
||||
"codeQL.monitorNewVariantAnalysis",
|
||||
processedVariantAnalysis,
|
||||
);
|
||||
}
|
||||
@@ -258,7 +265,7 @@ export class VariantAnalysisManager
|
||||
await this.setVariantAnalysis(variantAnalysis);
|
||||
|
||||
try {
|
||||
const repoStates = await readJson(
|
||||
const repoStates = await readRepoStates(
|
||||
this.getRepoStatesStoragePath(variantAnalysis.id),
|
||||
);
|
||||
this.repoStates.set(variantAnalysis.id, repoStates);
|
||||
@@ -274,7 +281,7 @@ export class VariantAnalysisManager
|
||||
))
|
||||
) {
|
||||
void this.app.commands.execute(
|
||||
"codeQL.monitorVariantAnalysis",
|
||||
"codeQL.monitorRehydratedVariantAnalysis",
|
||||
variantAnalysis,
|
||||
);
|
||||
}
|
||||
@@ -591,7 +598,7 @@ export class VariantAnalysisManager
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded;
|
||||
await this.onRepoStateUpdated(variantAnalysis.id, repoState);
|
||||
|
||||
await outputJson(
|
||||
await writeRepoStates(
|
||||
this.getRepoStatesStoragePath(variantAnalysis.id),
|
||||
this.repoStates.get(variantAnalysis.id),
|
||||
);
|
||||
@@ -696,7 +703,7 @@ export class VariantAnalysisManager
|
||||
private getRepoStatesStoragePath(variantAnalysisId: number): string {
|
||||
return join(
|
||||
this.getVariantAnalysisStorageLocation(variantAnalysisId),
|
||||
VariantAnalysisManager.REPO_STATES_FILENAME,
|
||||
REPO_STATES_FILENAME,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import * as React from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
ShowProgressMessage,
|
||||
ToDataExtensionsEditorMessage,
|
||||
} from "../../pure/interface-types";
|
||||
import {
|
||||
VSCodeDataGrid,
|
||||
VSCodeDataGridCell,
|
||||
VSCodeDataGridRow,
|
||||
} 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 { MethodRow } from "./MethodRow";
|
||||
import { assertNever } from "../../pure/helpers-pure";
|
||||
import { calculateSupportedPercentage } from "./supported";
|
||||
|
||||
export const DataExtensionsEditorContainer = styled.div`
|
||||
margin-top: 1rem;
|
||||
`;
|
||||
|
||||
type ProgressBarProps = {
|
||||
completion: number;
|
||||
};
|
||||
|
||||
const ProgressBar = styled.div<ProgressBarProps>`
|
||||
height: 10px;
|
||||
width: ${(props) => props.completion * 100}%;
|
||||
|
||||
background-color: var(--vscode-progressBar-background);
|
||||
`;
|
||||
|
||||
export function DataExtensionsEditor(): JSX.Element {
|
||||
const [externalApiUsages, setExternalApiUsages] = useState<
|
||||
ExternalApiUsage[]
|
||||
>([]);
|
||||
const [modeledMethods, setModeledMethods] = useState<
|
||||
Record<string, ModeledMethod>
|
||||
>({});
|
||||
const [progress, setProgress] = useState<Omit<ShowProgressMessage, "t">>({
|
||||
step: 0,
|
||||
maxStep: 0,
|
||||
message: "",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (evt: MessageEvent) => {
|
||||
if (evt.origin === window.origin) {
|
||||
const msg: ToDataExtensionsEditorMessage = evt.data;
|
||||
switch (msg.t) {
|
||||
case "setExternalApiUsages":
|
||||
setExternalApiUsages(msg.externalApiUsages);
|
||||
break;
|
||||
case "showProgress":
|
||||
setProgress(msg);
|
||||
break;
|
||||
default:
|
||||
assertNever(msg);
|
||||
}
|
||||
} 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 supportedPercentage = useMemo(
|
||||
() => calculateSupportedPercentage(externalApiUsages),
|
||||
[externalApiUsages],
|
||||
);
|
||||
|
||||
const unsupportedPercentage = 100 - supportedPercentage;
|
||||
|
||||
const onChange = useCallback(
|
||||
(method: ExternalApiUsage, model: ModeledMethod) => {
|
||||
setModeledMethods((oldModeledMethods) => ({
|
||||
...oldModeledMethods,
|
||||
[method.signature]: model,
|
||||
}));
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<DataExtensionsEditorContainer>
|
||||
{progress.maxStep > 0 && (
|
||||
<p>
|
||||
<ProgressBar completion={progress.step / progress.maxStep} />{" "}
|
||||
{progress.message}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{externalApiUsages.length > 0 && (
|
||||
<>
|
||||
<div>
|
||||
<h3>External API support stats</h3>
|
||||
<ul>
|
||||
<li>Supported: {supportedPercentage.toFixed(2)}%</li>
|
||||
<li>Unsupported: {unsupportedPercentage.toFixed(2)}%</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3>External API modelling</h3>
|
||||
<VSCodeDataGrid>
|
||||
<VSCodeDataGridRow rowType="header">
|
||||
<VSCodeDataGridCell cellType="columnheader" gridColumn={1}>
|
||||
Type
|
||||
</VSCodeDataGridCell>
|
||||
<VSCodeDataGridCell cellType="columnheader" gridColumn={2}>
|
||||
Method
|
||||
</VSCodeDataGridCell>
|
||||
<VSCodeDataGridCell cellType="columnheader" gridColumn={3}>
|
||||
Usages
|
||||
</VSCodeDataGridCell>
|
||||
<VSCodeDataGridCell cellType="columnheader" gridColumn={4}>
|
||||
Model type
|
||||
</VSCodeDataGridCell>
|
||||
<VSCodeDataGridCell cellType="columnheader" gridColumn={5}>
|
||||
Input
|
||||
</VSCodeDataGridCell>
|
||||
<VSCodeDataGridCell cellType="columnheader" gridColumn={6}>
|
||||
Output
|
||||
</VSCodeDataGridCell>
|
||||
<VSCodeDataGridCell cellType="columnheader" gridColumn={7}>
|
||||
Kind
|
||||
</VSCodeDataGridCell>
|
||||
</VSCodeDataGridRow>
|
||||
{externalApiUsages.map((externalApiUsage) => (
|
||||
<MethodRow
|
||||
key={externalApiUsage.signature}
|
||||
externalApiUsage={externalApiUsage}
|
||||
modeledMethod={modeledMethods[externalApiUsage.signature]}
|
||||
onChange={onChange}
|
||||
/>
|
||||
))}
|
||||
</VSCodeDataGrid>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</DataExtensionsEditorContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
import {
|
||||
VSCodeDataGridCell,
|
||||
VSCodeDataGridRow,
|
||||
VSCodeDropdown,
|
||||
VSCodeOption,
|
||||
VSCodeTextField,
|
||||
} from "@vscode/webview-ui-toolkit/react";
|
||||
import * as React from "react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
|
||||
import {
|
||||
ModeledMethod,
|
||||
ModeledMethodType,
|
||||
} from "../../data-extensions-editor/modeled-method";
|
||||
|
||||
const Dropdown = styled(VSCodeDropdown)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const TextField = styled(VSCodeTextField)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type SupportedUnsupportedSpanProps = {
|
||||
supported: boolean;
|
||||
};
|
||||
|
||||
const SupportSpan = styled.span<SupportedUnsupportedSpanProps>`
|
||||
color: ${(props) => (props.supported ? "green" : "red")};
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
externalApiUsage: ExternalApiUsage;
|
||||
modeledMethod: ModeledMethod | undefined;
|
||||
onChange: (
|
||||
externalApiUsage: ExternalApiUsage,
|
||||
modeledMethod: ModeledMethod,
|
||||
) => void;
|
||||
};
|
||||
|
||||
export const MethodRow = ({
|
||||
externalApiUsage,
|
||||
modeledMethod,
|
||||
onChange,
|
||||
}: Props) => {
|
||||
const argumentsList = useMemo(() => {
|
||||
if (externalApiUsage.methodParameters === "()") {
|
||||
return [];
|
||||
}
|
||||
return externalApiUsage.methodParameters
|
||||
.substring(1, externalApiUsage.methodParameters.length - 1)
|
||||
.split(",");
|
||||
}, [externalApiUsage.methodParameters]);
|
||||
|
||||
const handleTypeInput = useCallback(
|
||||
(e: InputEvent) => {
|
||||
const target = e.target as HTMLSelectElement;
|
||||
|
||||
onChange(externalApiUsage, {
|
||||
// If there are no arguments, we will default to "this", which is Argument[-1]
|
||||
input: argumentsList.length === 0 ? "Argument[-1]" : "Argument[0]",
|
||||
output: "ReturnType",
|
||||
kind: "value",
|
||||
...modeledMethod,
|
||||
type: target.value as ModeledMethodType,
|
||||
});
|
||||
},
|
||||
[onChange, externalApiUsage, modeledMethod, argumentsList],
|
||||
);
|
||||
const handleInputInput = useCallback(
|
||||
(e: InputEvent) => {
|
||||
if (!modeledMethod) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = e.target as HTMLSelectElement;
|
||||
|
||||
onChange(externalApiUsage, {
|
||||
...modeledMethod,
|
||||
input: target.value as ModeledMethod["input"],
|
||||
});
|
||||
},
|
||||
[onChange, externalApiUsage, modeledMethod],
|
||||
);
|
||||
const handleOutputInput = useCallback(
|
||||
(e: InputEvent) => {
|
||||
if (!modeledMethod) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = e.target as HTMLSelectElement;
|
||||
|
||||
onChange(externalApiUsage, {
|
||||
...modeledMethod,
|
||||
output: target.value as ModeledMethod["output"],
|
||||
});
|
||||
},
|
||||
[onChange, externalApiUsage, modeledMethod],
|
||||
);
|
||||
const handleKindInput = useCallback(
|
||||
(e: InputEvent) => {
|
||||
if (!modeledMethod) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = e.target as HTMLSelectElement;
|
||||
|
||||
onChange(externalApiUsage, {
|
||||
...modeledMethod,
|
||||
kind: target.value as ModeledMethod["kind"],
|
||||
});
|
||||
},
|
||||
[onChange, externalApiUsage, modeledMethod],
|
||||
);
|
||||
|
||||
return (
|
||||
<VSCodeDataGridRow>
|
||||
<VSCodeDataGridCell gridColumn={1}>
|
||||
<SupportSpan supported={externalApiUsage.supported}>
|
||||
{externalApiUsage.packageName}.{externalApiUsage.typeName}
|
||||
</SupportSpan>
|
||||
</VSCodeDataGridCell>
|
||||
<VSCodeDataGridCell gridColumn={2}>
|
||||
<SupportSpan supported={externalApiUsage.supported}>
|
||||
{externalApiUsage.methodName}
|
||||
{externalApiUsage.methodParameters}
|
||||
</SupportSpan>
|
||||
</VSCodeDataGridCell>
|
||||
<VSCodeDataGridCell gridColumn={3}>
|
||||
{externalApiUsage.usages.length}
|
||||
</VSCodeDataGridCell>
|
||||
<VSCodeDataGridCell gridColumn={4}>
|
||||
{(!externalApiUsage.supported ||
|
||||
(modeledMethod && modeledMethod?.type !== "none")) && (
|
||||
<Dropdown
|
||||
value={modeledMethod?.type ?? "none"}
|
||||
onInput={handleTypeInput}
|
||||
>
|
||||
<VSCodeOption value="none">Unmodelled</VSCodeOption>
|
||||
<VSCodeOption value="source">Source</VSCodeOption>
|
||||
<VSCodeOption value="sink">Sink</VSCodeOption>
|
||||
<VSCodeOption value="summary">Flow summary</VSCodeOption>
|
||||
<VSCodeOption value="neutral">Neutral</VSCodeOption>
|
||||
</Dropdown>
|
||||
)}
|
||||
</VSCodeDataGridCell>
|
||||
<VSCodeDataGridCell gridColumn={5}>
|
||||
{modeledMethod?.type &&
|
||||
["sink", "summary"].includes(modeledMethod?.type) && (
|
||||
<Dropdown value={modeledMethod?.input} onInput={handleInputInput}>
|
||||
<VSCodeOption value="Argument[-1]">
|
||||
Argument[-1]: this
|
||||
</VSCodeOption>
|
||||
{argumentsList.map((argument, index) => (
|
||||
<VSCodeOption key={argument} value={`Argument[${index}]`}>
|
||||
Argument[{index}]: {argument}
|
||||
</VSCodeOption>
|
||||
))}
|
||||
</Dropdown>
|
||||
)}
|
||||
</VSCodeDataGridCell>
|
||||
<VSCodeDataGridCell gridColumn={6}>
|
||||
{modeledMethod?.type &&
|
||||
["source", "summary"].includes(modeledMethod?.type) && (
|
||||
<Dropdown value={modeledMethod?.output} onInput={handleOutputInput}>
|
||||
<VSCodeOption value="ReturnValue">ReturnValue</VSCodeOption>
|
||||
<VSCodeOption value="Argument[-1]">
|
||||
Argument[-1]: this
|
||||
</VSCodeOption>
|
||||
{argumentsList.map((argument, index) => (
|
||||
<VSCodeOption key={argument} value={`Argument[${index}]`}>
|
||||
Argument[{index}]: {argument}
|
||||
</VSCodeOption>
|
||||
))}
|
||||
</Dropdown>
|
||||
)}
|
||||
</VSCodeDataGridCell>
|
||||
<VSCodeDataGridCell gridColumn={7}>
|
||||
{modeledMethod?.type &&
|
||||
["source", "sink", "summary"].includes(modeledMethod?.type) && (
|
||||
<TextField value={modeledMethod?.kind} onInput={handleKindInput} />
|
||||
)}
|
||||
</VSCodeDataGridCell>
|
||||
</VSCodeDataGridRow>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import { calculateSupportedPercentage } from "../supported";
|
||||
|
||||
describe("calculateSupportedPercentage", () => {
|
||||
it("when there are no external API usages", () => {
|
||||
expect(calculateSupportedPercentage([])).toBe(0);
|
||||
});
|
||||
|
||||
it("when there are is 1 supported external API usage", () => {
|
||||
expect(
|
||||
calculateSupportedPercentage([
|
||||
{
|
||||
supported: true,
|
||||
},
|
||||
]),
|
||||
).toBe(100);
|
||||
});
|
||||
|
||||
it("when there are is 1 unsupported external API usage", () => {
|
||||
expect(
|
||||
calculateSupportedPercentage([
|
||||
{
|
||||
supported: false,
|
||||
},
|
||||
]),
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
it("when there are multiple supporte and unsupported external API usage", () => {
|
||||
expect(
|
||||
calculateSupportedPercentage([
|
||||
{
|
||||
supported: false,
|
||||
},
|
||||
{
|
||||
supported: true,
|
||||
},
|
||||
{
|
||||
supported: false,
|
||||
},
|
||||
{
|
||||
supported: false,
|
||||
},
|
||||
{
|
||||
supported: true,
|
||||
},
|
||||
{
|
||||
supported: false,
|
||||
},
|
||||
]),
|
||||
).toBeCloseTo(33.33);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import * as React from "react";
|
||||
import { WebviewDefinition } from "../webview-definition";
|
||||
import { DataExtensionsEditor } from "./DataExtensionsEditor";
|
||||
|
||||
const definition: WebviewDefinition = {
|
||||
component: <DataExtensionsEditor />,
|
||||
};
|
||||
|
||||
export default definition;
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
|
||||
|
||||
export function calculateSupportedPercentage(
|
||||
externalApiUsages: Array<Pick<ExternalApiUsage, "supported">>,
|
||||
): number {
|
||||
if (externalApiUsages.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const supportedExternalApiUsages = externalApiUsages.filter(
|
||||
(m) => m.supported,
|
||||
);
|
||||
|
||||
const supportedRatio =
|
||||
supportedExternalApiUsages.length / externalApiUsages.length;
|
||||
return supportedRatio * 100;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
FromCompareViewMessage,
|
||||
FromDataExtensionsEditorMessage,
|
||||
FromResultsViewMsg,
|
||||
FromVariantAnalysisMessage,
|
||||
VariantAnalysisState,
|
||||
@@ -13,7 +14,8 @@ export interface VsCodeApi {
|
||||
msg:
|
||||
| FromResultsViewMsg
|
||||
| FromCompareViewMessage
|
||||
| FromVariantAnalysisMessage,
|
||||
| FromVariantAnalysisMessage
|
||||
| FromDataExtensionsEditorMessage,
|
||||
): void;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[
|
||||
"v2.12.5",
|
||||
"v2.12.6",
|
||||
"v2.11.6",
|
||||
"v2.7.6",
|
||||
"v2.8.5",
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
import { decodeBqrsToExternalApiUsages } from "../../../src/data-extensions-editor/bqrs";
|
||||
import { DecodedBqrsChunk } from "../../../src/pure/bqrs-cli-types";
|
||||
|
||||
describe("decodeBqrsToExternalApiUsages", () => {
|
||||
const chunk: DecodedBqrsChunk = {
|
||||
columns: [
|
||||
{ name: "apiName", kind: "String" },
|
||||
{ name: "supported", kind: "Boolean" },
|
||||
{ name: "usage", kind: "Entity" },
|
||||
],
|
||||
tuples: [
|
||||
[
|
||||
"java.io.PrintStream#println(String)",
|
||||
true,
|
||||
{
|
||||
label: "println(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 29,
|
||||
startColumn: 9,
|
||||
endLine: 29,
|
||||
endColumn: 49,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
"org.springframework.boot.SpringApplication#run(Class,String[])",
|
||||
false,
|
||||
{
|
||||
label: "run(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/Sql2oExampleApplication.java",
|
||||
startLine: 9,
|
||||
startColumn: 9,
|
||||
endLine: 9,
|
||||
endColumn: 66,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
"org.sql2o.Connection#createQuery(String)",
|
||||
true,
|
||||
{
|
||||
label: "createQuery(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 15,
|
||||
startColumn: 13,
|
||||
endLine: 15,
|
||||
endColumn: 56,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
"org.sql2o.Connection#createQuery(String)",
|
||||
true,
|
||||
{
|
||||
label: "createQuery(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 26,
|
||||
startColumn: 13,
|
||||
endLine: 26,
|
||||
endColumn: 39,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
"org.sql2o.Query#executeScalar(Class)",
|
||||
true,
|
||||
{
|
||||
label: "executeScalar(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 15,
|
||||
startColumn: 13,
|
||||
endLine: 15,
|
||||
endColumn: 85,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
"org.sql2o.Query#executeScalar(Class)",
|
||||
true,
|
||||
{
|
||||
label: "executeScalar(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 26,
|
||||
startColumn: 13,
|
||||
endLine: 26,
|
||||
endColumn: 68,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
"org.sql2o.Sql2o#open()",
|
||||
true,
|
||||
{
|
||||
label: "open(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 14,
|
||||
startColumn: 24,
|
||||
endLine: 14,
|
||||
endColumn: 35,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
"org.sql2o.Sql2o#open()",
|
||||
true,
|
||||
{
|
||||
label: "open(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 25,
|
||||
startColumn: 24,
|
||||
endLine: 25,
|
||||
endColumn: 35,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
"org.sql2o.Sql2o#Sql2o(String,String,String)",
|
||||
true,
|
||||
{
|
||||
label: "new Sql2o(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 10,
|
||||
startColumn: 33,
|
||||
endLine: 10,
|
||||
endColumn: 88,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
"org.sql2o.Sql2o#Sql2o(String)",
|
||||
true,
|
||||
{
|
||||
label: "new Sql2o(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 23,
|
||||
startColumn: 23,
|
||||
endLine: 23,
|
||||
endColumn: 36,
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
it("extracts api usages", () => {
|
||||
// Even though there are a number of usages with the same number of usages, the order returned should be stable:
|
||||
// - Iterating over a map (as done by .values()) is guaranteed to be in insertion order
|
||||
// - Sorting the array of usages is guaranteed to be a stable sort
|
||||
expect(decodeBqrsToExternalApiUsages(chunk)).toEqual([
|
||||
{
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Connection",
|
||||
methodName: "createQuery",
|
||||
methodParameters: "(String)",
|
||||
supported: true,
|
||||
usages: [
|
||||
{
|
||||
label: "createQuery(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 15,
|
||||
startColumn: 13,
|
||||
endLine: 15,
|
||||
endColumn: 56,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "createQuery(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 26,
|
||||
startColumn: 13,
|
||||
endLine: 26,
|
||||
endColumn: 39,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
signature: "org.sql2o.Query#executeScalar(Class)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Query",
|
||||
methodName: "executeScalar",
|
||||
methodParameters: "(Class)",
|
||||
supported: true,
|
||||
usages: [
|
||||
{
|
||||
label: "executeScalar(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 15,
|
||||
startColumn: 13,
|
||||
endLine: 15,
|
||||
endColumn: 85,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "executeScalar(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 26,
|
||||
startColumn: 13,
|
||||
endLine: 26,
|
||||
endColumn: 68,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
signature: "org.sql2o.Sql2o#open()",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "open",
|
||||
methodParameters: "()",
|
||||
supported: true,
|
||||
usages: [
|
||||
{
|
||||
label: "open(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 14,
|
||||
startColumn: 24,
|
||||
endLine: 14,
|
||||
endColumn: 35,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "open(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 25,
|
||||
startColumn: 24,
|
||||
endLine: 25,
|
||||
endColumn: 35,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
signature: "java.io.PrintStream#println(String)",
|
||||
packageName: "java.io",
|
||||
typeName: "PrintStream",
|
||||
methodName: "println",
|
||||
methodParameters: "(String)",
|
||||
supported: true,
|
||||
usages: [
|
||||
{
|
||||
label: "println(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 29,
|
||||
startColumn: 9,
|
||||
endLine: 29,
|
||||
endColumn: 49,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
signature:
|
||||
"org.springframework.boot.SpringApplication#run(Class,String[])",
|
||||
packageName: "org.springframework.boot",
|
||||
typeName: "SpringApplication",
|
||||
methodName: "run",
|
||||
methodParameters: "(Class,String[])",
|
||||
supported: false,
|
||||
usages: [
|
||||
{
|
||||
label: "run(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/Sql2oExampleApplication.java",
|
||||
startLine: 9,
|
||||
startColumn: 9,
|
||||
endLine: 9,
|
||||
endColumn: 66,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String,String,String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "Sql2o",
|
||||
methodParameters: "(String,String,String)",
|
||||
supported: true,
|
||||
usages: [
|
||||
{
|
||||
label: "new Sql2o(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 10,
|
||||
startColumn: 33,
|
||||
endLine: 10,
|
||||
endColumn: 88,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "Sql2o",
|
||||
methodParameters: "(String)",
|
||||
supported: true,
|
||||
usages: [
|
||||
{
|
||||
label: "new Sql2o(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 23,
|
||||
startColumn: 23,
|
||||
endLine: 23,
|
||||
endColumn: 36,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -516,7 +516,7 @@ describe("Variant Analysis Manager", () => {
|
||||
variantAnalysis,
|
||||
);
|
||||
expect(executeCommandSpy).toHaveBeenCalledWith(
|
||||
"codeQL.monitorVariantAnalysis",
|
||||
"codeQL.monitorRehydratedVariantAnalysis",
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -128,7 +128,7 @@ describe("Variant Analysis Manager", () => {
|
||||
);
|
||||
|
||||
expect(executeCommandSpy).toBeCalledWith(
|
||||
"codeQL.monitorVariantAnalysis",
|
||||
"codeQL.monitorNewVariantAnalysis",
|
||||
expect.objectContaining({
|
||||
id: mockApiResponse.id,
|
||||
status: VariantAnalysisStatus.InProgress,
|
||||
@@ -149,7 +149,7 @@ describe("Variant Analysis Manager", () => {
|
||||
);
|
||||
|
||||
expect(executeCommandSpy).toBeCalledWith(
|
||||
"codeQL.monitorVariantAnalysis",
|
||||
"codeQL.monitorNewVariantAnalysis",
|
||||
expect.objectContaining({
|
||||
id: mockApiResponse.id,
|
||||
status: VariantAnalysisStatus.InProgress,
|
||||
@@ -170,7 +170,7 @@ describe("Variant Analysis Manager", () => {
|
||||
);
|
||||
|
||||
expect(executeCommandSpy).toBeCalledWith(
|
||||
"codeQL.monitorVariantAnalysis",
|
||||
"codeQL.monitorNewVariantAnalysis",
|
||||
expect.objectContaining({
|
||||
id: mockApiResponse.id,
|
||||
status: VariantAnalysisStatus.InProgress,
|
||||
@@ -293,7 +293,7 @@ describe("Variant Analysis Manager", () => {
|
||||
|
||||
expect(mockSubmitVariantAnalysis).toBeCalledTimes(1);
|
||||
expect(executeCommandSpy).toBeCalledWith(
|
||||
"codeQL.monitorVariantAnalysis",
|
||||
"codeQL.monitorNewVariantAnalysis",
|
||||
expect.objectContaining({
|
||||
query: expect.objectContaining({ filePath: fileUri.fsPath }),
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user