Merge remote-tracking branch 'origin/main' into koesie10/improve-skeleton-db-download
This commit is contained in:
2
.github/workflows/cli-test.yml
vendored
2
.github/workflows/cli-test.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: extensions/ql-vscode/.nvmrc
|
||||
cache: 'npm'
|
||||
|
||||
12
.github/workflows/main.yml
vendored
12
.github/workflows/main.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: extensions/ql-vscode/.nvmrc
|
||||
cache: 'npm'
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: extensions/ql-vscode/.nvmrc
|
||||
cache: 'npm'
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: extensions/ql-vscode/.nvmrc
|
||||
cache: 'npm'
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: extensions/ql-vscode/.nvmrc
|
||||
cache: 'npm'
|
||||
@@ -183,7 +183,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: extensions/ql-vscode/.nvmrc
|
||||
cache: 'npm'
|
||||
@@ -251,7 +251,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: extensions/ql-vscode/.nvmrc
|
||||
cache: 'npm'
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: extensions/ql-vscode/.nvmrc
|
||||
|
||||
|
||||
@@ -152,11 +152,14 @@ Run one of the above MRVAs, but cancel it from within VS Code:
|
||||
- Check that the editor loads and shows methods to model.
|
||||
- Check that methods are grouped per library (e.g. `rocksdbjni@7.7.3` or `asm@6.0`)
|
||||
- Check that the "Open database" link works.
|
||||
- Check that the 'View' button works and the Method Usage panel highlight the correct method and usage
|
||||
- Check that the Method Modeling panel shows the correct method and modeling state
|
||||
|
||||
#### Test Case 2: Model methods
|
||||
|
||||
1. Expand one of the libraries.
|
||||
- Change the model type and check that the other dropdowns change.
|
||||
- Check that the method modeling panel updates accordingly
|
||||
2. Save the modeled methods.
|
||||
3. Click "Open extension pack"
|
||||
- Check that the file explorer opens a directory with a "models" directory
|
||||
@@ -189,9 +192,28 @@ Are there any components that are not showing up?
|
||||
|
||||
## Optional Test Cases
|
||||
|
||||
These are mostly aimed at MRVA, but some of them are also applicable to non-MRVA queries.
|
||||
### Modeling Flow
|
||||
|
||||
### Selecting repositories to run on
|
||||
1. Check that a method can have multiple models:
|
||||
- Add a couple of new models for one method in the model editor
|
||||
- Save and check that the modeling file (use the 'open extension pack' button to open it) shows multiple methods
|
||||
- Check that the Method Modeling Panel shows the correct multiple models
|
||||
- Check that you can browse through different models in the Method Modeling Panel
|
||||
- Check that a 'duplicated classification' error appears in both model editor and modeling panel when a duplicate modeling occurs
|
||||
- Check that a 'conflicting classification' error appears when a neutral model type is paired with a model of the same kind
|
||||
- Check that clicking on the error highlights the correct modeling in both the editor and the modeling panel
|
||||
2. Check the Method Usage Panel
|
||||
- Check that the Method Usage Panel opens and jumps to the correct usage when clicking on 'View' in the model editor
|
||||
- Check that the first and following usages are opening when clicking on a usage
|
||||
- Check that the usage icon color turns green when saving a newly modeled method
|
||||
- Check that the usage icon color turns red when saving a newly unmodeld method
|
||||
3. Check the Method Modeling Panel
|
||||
- Check that the 'Start modeling' button opens a new model editor
|
||||
- Check that it refreshes the blank state when a model editor is opened/closed
|
||||
- Check that when modeling in the editor the modeling panel updates accordingly
|
||||
- Check that when modeling in the modeling panel the model editor updates accordingly
|
||||
|
||||
### Selecting MRVA repositories to run on
|
||||
|
||||
#### Test case 1: Running a query on a single repository
|
||||
|
||||
@@ -221,7 +243,7 @@ These are mostly aimed at MRVA, but some of them are also applicable to non-MRVA
|
||||
4. The org contains private repositories that are inaccessible
|
||||
2. The org does not exist
|
||||
|
||||
### Using different types of controller repos
|
||||
### Using different types of controller repos for MRVA
|
||||
|
||||
#### Test case 1: Running a query when the controller repository is public
|
||||
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
## 1.9.3 - 26 October 2023
|
||||
|
||||
- Sorted result set filenames now include a hash of the result set name instead of the full name. [#2955](https://github.com/github/vscode-codeql/pull/2955)
|
||||
- The "Install Pack Dependencies" will now only list CodeQL packs located in the workspace. [#2960](https://github.com/github/vscode-codeql/pull/2960)
|
||||
- Fix a bug where the "View Query Log" action for a query history item was not working. [#2984](https://github.com/github/vscode-codeql/pull/2984)
|
||||
- Add a command to sort items in the databases view by language. [#2993](https://github.com/github/vscode-codeql/pull/2993)
|
||||
- Fix not being able to open the results directory or evaluator log for a cancelled local query run. [#2996](https://github.com/github/vscode-codeql/pull/2996)
|
||||
- Fix empty row in alert path when the SARIF location was empty. [#3018](https://github.com/github/vscode-codeql/pull/3018)
|
||||
|
||||
## 1.9.2 - 12 October 2023
|
||||
|
||||
|
||||
4
extensions/ql-vscode/package-lock.json
generated
4
extensions/ql-vscode/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "vscode-codeql",
|
||||
"version": "1.9.3",
|
||||
"version": "1.9.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "vscode-codeql",
|
||||
"version": "1.9.3",
|
||||
"version": "1.9.4",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "CodeQL for Visual Studio Code",
|
||||
"author": "GitHub",
|
||||
"private": true,
|
||||
"version": "1.9.3",
|
||||
"version": "1.9.4",
|
||||
"publisher": "GitHub",
|
||||
"license": "MIT",
|
||||
"icon": "media/VS-marketplace-CodeQL-icon.png",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as Sarif from "sarif";
|
||||
import type { HighlightedRegion } from "../variant-analysis/shared/analysis-result";
|
||||
import { ResolvableLocationValue } from "../common/bqrs-cli-types";
|
||||
import { isEmptyPath } from "./bqrs-utils";
|
||||
|
||||
export interface SarifLink {
|
||||
dest: number;
|
||||
@@ -111,6 +112,9 @@ export function parseSarifLocation(
|
||||
return { hint: "no artifact location" };
|
||||
if (physicalLocation.artifactLocation.uri === undefined)
|
||||
return { hint: "artifact location has no uri" };
|
||||
if (isEmptyPath(physicalLocation.artifactLocation.uri)) {
|
||||
return { hint: "artifact location has empty uri" };
|
||||
}
|
||||
|
||||
// This is not necessarily really an absolute uri; it could either be a
|
||||
// file uri or a relative uri.
|
||||
|
||||
@@ -707,7 +707,6 @@ const LLM_GENERATION_BATCH_SIZE = new Setting(
|
||||
MODEL_SETTING,
|
||||
);
|
||||
const EXTENSIONS_DIRECTORY = new Setting("extensionsDirectory", MODEL_SETTING);
|
||||
const SHOW_MULTIPLE_MODELS = new Setting("showMultipleModels", MODEL_SETTING);
|
||||
|
||||
export interface ModelConfig {
|
||||
flowGeneration: boolean;
|
||||
@@ -744,6 +743,6 @@ export class ModelConfigListener extends ConfigListener implements ModelConfig {
|
||||
}
|
||||
|
||||
public get showMultipleModels(): boolean {
|
||||
return !!SHOW_MULTIPLE_MODELS.getValue<boolean>();
|
||||
return isCanary();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@ import {
|
||||
getFirstWorkspaceFolder,
|
||||
isFolderAlreadyInWorkspace,
|
||||
} from "../../common/vscode/workspace-folders";
|
||||
import { isQueryLanguage } from "../../common/query-language";
|
||||
import {
|
||||
isQueryLanguage,
|
||||
tryGetQueryLanguage,
|
||||
} from "../../common/query-language";
|
||||
import { existsSync } from "fs";
|
||||
import { QlPackGenerator } from "../../local-queries/qlpack-generator";
|
||||
import { asError, getErrorMessage } from "../../common/helpers-pure";
|
||||
@@ -30,6 +33,7 @@ import { containsPath } from "../../common/files";
|
||||
import { DatabaseChangedEvent, DatabaseEventKind } from "./database-events";
|
||||
import { DatabaseResolver } from "./database-resolver";
|
||||
import { telemetryListener } from "../../common/vscode/telemetry";
|
||||
import { LanguageContextStore } from "../../language-context-store";
|
||||
|
||||
/**
|
||||
* The name of the key in the workspaceState dictionary in which we
|
||||
@@ -100,11 +104,25 @@ export class DatabaseManager extends DisposableObject {
|
||||
private readonly app: App,
|
||||
private readonly qs: QueryRunner,
|
||||
private readonly cli: cli.CodeQLCliServer,
|
||||
private readonly languageContext: LanguageContextStore,
|
||||
public logger: Logger,
|
||||
) {
|
||||
super();
|
||||
|
||||
qs.onStart(this.reregisterDatabases.bind(this));
|
||||
|
||||
this.push(
|
||||
this.languageContext.onLanguageContextChanged(async () => {
|
||||
if (
|
||||
this.currentDatabaseItem !== undefined &&
|
||||
!this.languageContext.isSelectedLanguage(
|
||||
tryGetQueryLanguage(this.currentDatabaseItem.language),
|
||||
)
|
||||
) {
|
||||
await this.setCurrentDatabaseItem(undefined);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -769,14 +769,6 @@ async function activateWithInstalledDistribution(
|
||||
fsWatcher.onDidDelete(clearPackCache);
|
||||
}
|
||||
|
||||
void extLogger.log("Initializing database manager.");
|
||||
const dbm = new DatabaseManager(ctx, app, qs, cliServer, extLogger);
|
||||
|
||||
// Let this run async.
|
||||
void dbm.loadPersistedState();
|
||||
|
||||
ctx.subscriptions.push(dbm);
|
||||
|
||||
void extLogger.log("Initializing language context.");
|
||||
const languageContext = new LanguageContextStore(app);
|
||||
|
||||
@@ -784,6 +776,21 @@ async function activateWithInstalledDistribution(
|
||||
const languageSelectionPanel = new LanguageSelectionPanel(languageContext);
|
||||
ctx.subscriptions.push(languageSelectionPanel);
|
||||
|
||||
void extLogger.log("Initializing database manager.");
|
||||
const dbm = new DatabaseManager(
|
||||
ctx,
|
||||
app,
|
||||
qs,
|
||||
cliServer,
|
||||
languageContext,
|
||||
extLogger,
|
||||
);
|
||||
|
||||
// Let this run async.
|
||||
void dbm.loadPersistedState();
|
||||
|
||||
ctx.subscriptions.push(dbm);
|
||||
|
||||
void extLogger.log("Initializing database panel.");
|
||||
const databaseUI = new DatabaseUI(
|
||||
app,
|
||||
@@ -795,7 +802,11 @@ async function activateWithInstalledDistribution(
|
||||
);
|
||||
ctx.subscriptions.push(databaseUI);
|
||||
|
||||
QueriesModule.initialize(app, languageContext, cliServer);
|
||||
const queriesModule = QueriesModule.initialize(
|
||||
app,
|
||||
languageContext,
|
||||
cliServer,
|
||||
);
|
||||
|
||||
void extLogger.log("Initializing evaluator log viewer.");
|
||||
const evalLogViewer = new EvalLogViewer();
|
||||
@@ -934,6 +945,10 @@ async function activateWithInstalledDistribution(
|
||||
);
|
||||
ctx.subscriptions.push(localQueries);
|
||||
|
||||
queriesModule.onDidChangeSelection((event) =>
|
||||
localQueries.setSelectedQueryTreeViewItems(event.selection),
|
||||
);
|
||||
|
||||
void extLogger.log("Initializing debugger factory.");
|
||||
ctx.subscriptions.push(
|
||||
new QLDebugAdapterDescriptorFactory(queryStorageDir, qs, localQueries),
|
||||
|
||||
@@ -63,6 +63,8 @@ export enum QuickEvalType {
|
||||
}
|
||||
|
||||
export class LocalQueries extends DisposableObject {
|
||||
private selectedQueryTreeViewItems: readonly QueryTreeViewItem[] = [];
|
||||
|
||||
public constructor(
|
||||
private readonly app: App,
|
||||
private readonly queryRunner: QueryRunner,
|
||||
@@ -77,6 +79,12 @@ export class LocalQueries extends DisposableObject {
|
||||
super();
|
||||
}
|
||||
|
||||
public setSelectedQueryTreeViewItems(
|
||||
selection: readonly QueryTreeViewItem[],
|
||||
) {
|
||||
this.selectedQueryTreeViewItems = selection;
|
||||
}
|
||||
|
||||
public getCommands(): LocalQueryCommands {
|
||||
return {
|
||||
"codeQL.runQuery": this.runQuery.bind(this),
|
||||
@@ -333,6 +341,7 @@ export class LocalQueries extends DisposableObject {
|
||||
this.app,
|
||||
this.databaseManager,
|
||||
contextStoragePath,
|
||||
this.selectedQueryTreeViewItems,
|
||||
language,
|
||||
);
|
||||
await skeletonQueryWizard.execute();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { join } from "path";
|
||||
import { Uri, window as Window, window, workspace } from "vscode";
|
||||
import { basename, dirname, join } from "path";
|
||||
import { Uri, window, window as Window, workspace } from "vscode";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { showAndLogExceptionWithTelemetry } from "../common/logging";
|
||||
import { Credentials } from "../common/authentication";
|
||||
@@ -7,10 +7,7 @@ import {
|
||||
getLanguageDisplayName,
|
||||
QueryLanguage,
|
||||
} from "../common/query-language";
|
||||
import {
|
||||
getFirstWorkspaceFolder,
|
||||
isFolderAlreadyInWorkspace,
|
||||
} from "../common/vscode/workspace-folders";
|
||||
import { getFirstWorkspaceFolder } from "../common/vscode/workspace-folders";
|
||||
import { asError, getErrorMessage } from "../common/helpers-pure";
|
||||
import { QlPackGenerator } from "./qlpack-generator";
|
||||
import { DatabaseItem, DatabaseManager } from "../databases/local-databases";
|
||||
@@ -28,11 +25,12 @@ import {
|
||||
isCodespacesTemplate,
|
||||
setQlPackLocation,
|
||||
} from "../config";
|
||||
import { existsSync } from "fs-extra";
|
||||
import { lstat, pathExists } from "fs-extra";
|
||||
import { askForLanguage } from "../codeql-cli/query-language";
|
||||
import { showInformationMessageWithAction } from "../common/vscode/dialog";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { App } from "../common/app";
|
||||
import { QueryTreeViewItem } from "../queries-panel/query-tree-view-item";
|
||||
|
||||
type QueryLanguagesToDatabaseMap = Record<string, string>;
|
||||
|
||||
@@ -59,6 +57,7 @@ export class SkeletonQueryWizard {
|
||||
private readonly app: App,
|
||||
private readonly databaseManager: DatabaseManager,
|
||||
private readonly databaseStoragePath: string | undefined,
|
||||
private readonly selectedItems: readonly QueryTreeViewItem[],
|
||||
private language: QueryLanguage | undefined = undefined,
|
||||
) {}
|
||||
|
||||
@@ -88,9 +87,9 @@ export class SkeletonQueryWizard {
|
||||
|
||||
this.qlPackStoragePath = await this.determineStoragePath();
|
||||
|
||||
const skeletonPackAlreadyExists =
|
||||
existsSync(join(this.qlPackStoragePath, this.folderName)) ||
|
||||
isFolderAlreadyInWorkspace(this.folderName);
|
||||
const skeletonPackAlreadyExists = await pathExists(
|
||||
join(this.qlPackStoragePath, this.folderName),
|
||||
);
|
||||
|
||||
if (skeletonPackAlreadyExists) {
|
||||
// just create a new example query file in skeleton QL pack
|
||||
@@ -129,7 +128,41 @@ export class SkeletonQueryWizard {
|
||||
});
|
||||
}
|
||||
|
||||
public async determineStoragePath() {
|
||||
public async determineStoragePath(): Promise<string> {
|
||||
if (this.selectedItems.length === 0) {
|
||||
return this.determineRootStoragePath();
|
||||
}
|
||||
|
||||
const storagePath = await this.determineStoragePathFromSelection();
|
||||
|
||||
// If the user has selected a folder or file within a folder that matches the current
|
||||
// folder name, we should create a query rather than a query pack
|
||||
if (basename(storagePath) === this.folderName) {
|
||||
return dirname(storagePath);
|
||||
}
|
||||
|
||||
return storagePath;
|
||||
}
|
||||
|
||||
private async determineStoragePathFromSelection(): Promise<string> {
|
||||
// Just like VS Code's "New File" command, if the user has selected multiple files/folders in the queries panel,
|
||||
// we will create the new file in the same folder as the first selected item.
|
||||
// See https://github.com/microsoft/vscode/blob/a8b7239d0311d4915b57c837972baf4b01394491/src/vs/workbench/contrib/files/browser/fileActions.ts#L893-L900
|
||||
const selectedItem = this.selectedItems[0];
|
||||
|
||||
const path = selectedItem.path;
|
||||
|
||||
// We use stat to protect against outdated query tree items
|
||||
const fileStat = await lstat(path);
|
||||
|
||||
if (fileStat.isDirectory()) {
|
||||
return path;
|
||||
}
|
||||
|
||||
return dirname(path);
|
||||
}
|
||||
|
||||
public async determineRootStoragePath() {
|
||||
const firstStorageFolder = getFirstWorkspaceFolder();
|
||||
|
||||
if (isCodespacesTemplate()) {
|
||||
@@ -138,7 +171,7 @@ export class SkeletonQueryWizard {
|
||||
|
||||
let storageFolder = getQlPackLocation();
|
||||
|
||||
if (storageFolder === undefined || !existsSync(storageFolder)) {
|
||||
if (storageFolder === undefined || !(await pathExists(storageFolder))) {
|
||||
storageFolder = await Window.showInputBox({
|
||||
title:
|
||||
"Please choose a folder in which to create your new query pack. You can change this in the extension settings.",
|
||||
@@ -151,7 +184,7 @@ export class SkeletonQueryWizard {
|
||||
throw new UserCancellationException("No storage folder entered.");
|
||||
}
|
||||
|
||||
if (!existsSync(storageFolder)) {
|
||||
if (!(await pathExists(storageFolder))) {
|
||||
throw new UserCancellationException(
|
||||
"Invalid folder. Must be a folder that already exists.",
|
||||
);
|
||||
@@ -228,7 +261,7 @@ export class SkeletonQueryWizard {
|
||||
await qlPackGenerator.createExampleQlFile(this.fileName);
|
||||
} catch (e: unknown) {
|
||||
void this.app.logger.log(
|
||||
`Could not create skeleton QL pack: ${getErrorMessage(e)}`,
|
||||
`Could not create query example file: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||
import { extLogger } from "../common/logging/vscode";
|
||||
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 { 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 { QueryLanguage } from "../common/query-language";
|
||||
import { fetchExternalApiQueries } from "./queries";
|
||||
import { Method } from "./method";
|
||||
import { runQuery } from "../local-queries/run-query";
|
||||
import { decodeBqrsToMethods } from "./bqrs";
|
||||
import {
|
||||
resolveEndpointsQuery,
|
||||
syntheticQueryPackName,
|
||||
} from "./model-editor-queries";
|
||||
|
||||
type RunQueryOptions = {
|
||||
cliServer: CodeQLCliServer;
|
||||
queryRunner: QueryRunner;
|
||||
databaseItem: DatabaseItem;
|
||||
queryStorageDir: string;
|
||||
queryDir: string;
|
||||
|
||||
progress: ProgressCallback;
|
||||
token: CancellationToken;
|
||||
};
|
||||
|
||||
export async function prepareExternalApiQuery(
|
||||
queryDir: string,
|
||||
language: QueryLanguage,
|
||||
): 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");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export const externalApiQueriesProgressMaxStep = 2000;
|
||||
|
||||
export async function runExternalApiQueries(
|
||||
mode: Mode,
|
||||
{
|
||||
cliServer,
|
||||
queryRunner,
|
||||
databaseItem,
|
||||
queryStorageDir,
|
||||
queryDir,
|
||||
progress,
|
||||
token,
|
||||
}: RunQueryOptions,
|
||||
): Promise<Method[] | 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.
|
||||
// For a reference of what this should do in the future, see the previous implementation in
|
||||
// https://github.com/github/vscode-codeql/blob/089d3566ef0bc67d9b7cc66e8fd6740b31c1c0b0/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts#L33-L72
|
||||
|
||||
progress({
|
||||
message: "Resolving QL packs",
|
||||
step: 1,
|
||||
maxStep: externalApiQueriesProgressMaxStep,
|
||||
});
|
||||
const additionalPacks = getOnDiskWorkspaceFolders();
|
||||
const extensionPacks = Object.keys(
|
||||
await cliServer.resolveQlpacks(additionalPacks, true),
|
||||
);
|
||||
|
||||
progress({
|
||||
message: "Resolving query",
|
||||
step: 2,
|
||||
maxStep: externalApiQueriesProgressMaxStep,
|
||||
});
|
||||
|
||||
// Resolve the queries from either codeql/java-queries or from the temporary queryDir
|
||||
const queryPath = await resolveEndpointsQuery(
|
||||
cliServer,
|
||||
databaseItem.language,
|
||||
mode,
|
||||
[syntheticQueryPackName],
|
||||
[queryDir],
|
||||
);
|
||||
if (!queryPath) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
extLogger,
|
||||
telemetryListener,
|
||||
redactableError`The ${mode} model editor query could not be found. Try re-opening the model editor. If that doesn't work, try upgrading the CodeQL libraries.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Run the actual query
|
||||
const completedQuery = await runQuery({
|
||||
queryRunner,
|
||||
databaseItem,
|
||||
queryPath,
|
||||
queryStorageDir,
|
||||
additionalPacks,
|
||||
extensionPacks,
|
||||
progress: (update) =>
|
||||
progress({
|
||||
step: update.step + 500,
|
||||
maxStep: externalApiQueriesProgressMaxStep,
|
||||
message: update.message,
|
||||
}),
|
||||
token,
|
||||
});
|
||||
|
||||
if (!completedQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the results and covert to internal representation
|
||||
progress({
|
||||
message: "Decoding results",
|
||||
step: 1600,
|
||||
maxStep: externalApiQueriesProgressMaxStep,
|
||||
});
|
||||
|
||||
const bqrsChunk = await readQueryResults({
|
||||
cliServer,
|
||||
bqrsPath: completedQuery.outputDir.bqrsPath,
|
||||
});
|
||||
if (!bqrsChunk) {
|
||||
return;
|
||||
}
|
||||
|
||||
progress({
|
||||
message: "Finalizing results",
|
||||
step: 1950,
|
||||
maxStep: externalApiQueriesProgressMaxStep,
|
||||
});
|
||||
|
||||
return decodeBqrsToMethods(bqrsChunk, mode);
|
||||
}
|
||||
|
||||
type GetResultsOptions = {
|
||||
cliServer: Pick<CodeQLCliServer, "bqrsInfo" | "bqrsDecode">;
|
||||
bqrsPath: string;
|
||||
};
|
||||
|
||||
export async function readQueryResults({
|
||||
cliServer,
|
||||
bqrsPath,
|
||||
}: GetResultsOptions) {
|
||||
const bqrsInfo = await cliServer.bqrsInfo(bqrsPath);
|
||||
if (bqrsInfo["result-sets"].length !== 1) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
extLogger,
|
||||
telemetryListener,
|
||||
redactableError`Expected exactly one result set, got ${bqrsInfo["result-sets"].length}`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const resultSet = bqrsInfo["result-sets"][0];
|
||||
|
||||
return cliServer.bqrsDecode(bqrsPath, resultSet.name);
|
||||
}
|
||||
|
||||
function queryNameFromMode(mode: Mode): string {
|
||||
return `${mode.charAt(0).toUpperCase() + mode.slice(1)}ModeEndpoints.ql`;
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import { isQueryLanguage } from "../common/query-language";
|
||||
import { DisposableObject } from "../common/disposable-object";
|
||||
import { MethodsUsagePanel } from "./methods-usage/methods-usage-panel";
|
||||
import { Method, Usage } from "./method";
|
||||
import { setUpPack } from "./model-editor-queries";
|
||||
import { setUpPack } from "./model-editor-queries-setup";
|
||||
import { MethodModelingPanel } from "./method-modeling/method-modeling-panel";
|
||||
import { ModelingStore } from "./modeling-store";
|
||||
import { showResolvableLocation } from "../databases/local-databases/locations";
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import { join } from "path";
|
||||
import { QueryLanguage } from "../common/query-language";
|
||||
import { writeFile } from "fs-extra";
|
||||
import { dump } from "js-yaml";
|
||||
import { prepareModelEditorQueries } from "./model-editor-queries";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { ModelConfig } from "../config";
|
||||
import { Mode } from "./shared/mode";
|
||||
import { resolveQueriesFromPacks } from "../local-queries";
|
||||
import { modeTag } from "./mode-tag";
|
||||
|
||||
export const syntheticQueryPackName = "codeql/model-editor-queries";
|
||||
|
||||
/**
|
||||
* setUpPack sets up a directory to use for the data extension editor queries if required.
|
||||
*
|
||||
* There are two cases (example language is Java):
|
||||
* - In case the queries are present in the codeql/java-queries, we don't need to write our own queries
|
||||
* to disk. We still need to create a synthetic query pack so we can pass the queryDir to the query
|
||||
* resolver without caring about whether the queries are present in the pack or not.
|
||||
* - In case the queries are not present in the codeql/java-queries, we need to write our own queries
|
||||
* to disk. We will create a synthetic query pack and install its dependencies so it is fully independent
|
||||
* and we can simply pass it through when resolving the queries.
|
||||
*
|
||||
* These steps together ensure that later steps of the process don't need to keep track of whether the queries
|
||||
* are present in codeql/java-queries or in our own query pack. They just need to resolve the query.
|
||||
*
|
||||
* @param cliServer The CodeQL CLI server to use.
|
||||
* @param queryDir The directory to set up.
|
||||
* @param language The language to use for the queries.
|
||||
* @param modelConfig The model config to use.
|
||||
* @returns true if the setup was successful, false otherwise.
|
||||
*/
|
||||
export async function setUpPack(
|
||||
cliServer: CodeQLCliServer,
|
||||
queryDir: string,
|
||||
language: QueryLanguage,
|
||||
modelConfig: ModelConfig,
|
||||
): Promise<boolean> {
|
||||
// Download the required query packs
|
||||
await cliServer.packDownload([`codeql/${language}-queries`]);
|
||||
|
||||
// We'll only check if the application mode query exists in the pack and assume that if it does,
|
||||
// the framework mode query will also exist.
|
||||
const applicationModeQuery = await resolveEndpointsQuery(
|
||||
cliServer,
|
||||
language,
|
||||
Mode.Application,
|
||||
[],
|
||||
[],
|
||||
);
|
||||
|
||||
if (applicationModeQuery) {
|
||||
// Set up a synthetic pack so CodeQL doesn't crash later when we try
|
||||
// to resolve a query within this directory
|
||||
const syntheticQueryPack = {
|
||||
name: syntheticQueryPackName,
|
||||
version: "0.0.0",
|
||||
dependencies: {},
|
||||
};
|
||||
|
||||
const qlpackFile = join(queryDir, "codeql-pack.yml");
|
||||
await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8");
|
||||
} else {
|
||||
// If we can't resolve the query, we need to write them to desk ourselves.
|
||||
const externalApiQuerySuccess = await prepareModelEditorQueries(
|
||||
queryDir,
|
||||
language,
|
||||
);
|
||||
if (!externalApiQuerySuccess) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up a synthetic pack so that the query can be resolved later.
|
||||
const syntheticQueryPack = {
|
||||
name: syntheticQueryPackName,
|
||||
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);
|
||||
}
|
||||
|
||||
// Download any other required packs
|
||||
if (language === "java" && modelConfig.llmGeneration) {
|
||||
await cliServer.packDownload([`codeql/${language}-automodel-queries`]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the query path to the model editor endpoints query. All queries are tagged like this:
|
||||
* modeleditor endpoints <mode>
|
||||
* Example: modeleditor endpoints framework-mode
|
||||
*
|
||||
* @param cliServer The CodeQL CLI server to use.
|
||||
* @param language The language of the query pack to use.
|
||||
* @param mode The mode to resolve the query for.
|
||||
* @param additionalPackNames Additional pack names to search.
|
||||
* @param additionalPackPaths Additional pack paths to search.
|
||||
*/
|
||||
export async function resolveEndpointsQuery(
|
||||
cliServer: CodeQLCliServer,
|
||||
language: string,
|
||||
mode: Mode,
|
||||
additionalPackNames: string[] = [],
|
||||
additionalPackPaths: string[] = [],
|
||||
): Promise<string | undefined> {
|
||||
const packsToSearch = [`codeql/${language}-queries`, ...additionalPackNames];
|
||||
|
||||
// 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 resolveQueriesFromPacks(
|
||||
cliServer,
|
||||
packsToSearch,
|
||||
{
|
||||
kind: "table",
|
||||
"tags contain all": ["modeleditor", "endpoints", modeTag(mode)],
|
||||
},
|
||||
additionalPackPaths,
|
||||
);
|
||||
if (queries.length > 1) {
|
||||
throw new Error(
|
||||
`Found multiple endpoints queries for ${mode}. Can't continue`,
|
||||
);
|
||||
}
|
||||
|
||||
if (queries.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return queries[0];
|
||||
}
|
||||
@@ -1,140 +1,189 @@
|
||||
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 { QueryRunner } from "../query-server";
|
||||
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||
import { extLogger } from "../common/logging/vscode";
|
||||
import { showAndLogExceptionWithTelemetry } from "../common/logging";
|
||||
import { CancellationToken } from "vscode";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { ModelConfig } from "../config";
|
||||
import { DatabaseItem } from "../databases/local-databases";
|
||||
import { ProgressCallback } from "../common/vscode/progress";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { telemetryListener } from "../common/vscode/telemetry";
|
||||
import { join } from "path";
|
||||
import { Mode } from "./shared/mode";
|
||||
import { resolveQueriesFromPacks } from "../local-queries";
|
||||
import { modeTag } from "./mode-tag";
|
||||
import { writeFile } from "fs-extra";
|
||||
import { QueryLanguage } from "../common/query-language";
|
||||
import { fetchExternalApiQueries } from "./queries";
|
||||
import { Method } from "./method";
|
||||
import { runQuery } from "../local-queries/run-query";
|
||||
import { decodeBqrsToMethods } from "./bqrs";
|
||||
import {
|
||||
resolveEndpointsQuery,
|
||||
syntheticQueryPackName,
|
||||
} from "./model-editor-queries-setup";
|
||||
|
||||
export const syntheticQueryPackName = "codeql/external-api-usage";
|
||||
type RunQueryOptions = {
|
||||
cliServer: CodeQLCliServer;
|
||||
queryRunner: QueryRunner;
|
||||
databaseItem: DatabaseItem;
|
||||
queryStorageDir: string;
|
||||
queryDir: string;
|
||||
|
||||
/**
|
||||
* setUpPack sets up a directory to use for the data extension editor queries if required.
|
||||
*
|
||||
* There are two cases (example language is Java):
|
||||
* - In case the queries are present in the codeql/java-queries, we don't need to write our own queries
|
||||
* to disk. We still need to create a synthetic query pack so we can pass the queryDir to the query
|
||||
* resolver without caring about whether the queries are present in the pack or not.
|
||||
* - In case the queries are not present in the codeql/java-queries, we need to write our own queries
|
||||
* to disk. We will create a synthetic query pack and install its dependencies so it is fully independent
|
||||
* and we can simply pass it through when resolving the queries.
|
||||
*
|
||||
* These steps together ensure that later steps of the process don't need to keep track of whether the queries
|
||||
* are present in codeql/java-queries or in our own query pack. They just need to resolve the query.
|
||||
*
|
||||
* @param cliServer The CodeQL CLI server to use.
|
||||
* @param queryDir The directory to set up.
|
||||
* @param language The language to use for the queries.
|
||||
* @param modelConfig The model config to use.
|
||||
* @returns true if the setup was successful, false otherwise.
|
||||
*/
|
||||
export async function setUpPack(
|
||||
cliServer: CodeQLCliServer,
|
||||
progress: ProgressCallback;
|
||||
token: CancellationToken;
|
||||
};
|
||||
|
||||
export async function prepareModelEditorQueries(
|
||||
queryDir: string,
|
||||
language: QueryLanguage,
|
||||
modelConfig: ModelConfig,
|
||||
): Promise<boolean> {
|
||||
// Download the required query packs
|
||||
await cliServer.packDownload([`codeql/${language}-queries`]);
|
||||
|
||||
// We'll only check if the application mode query exists in the pack and assume that if it does,
|
||||
// the framework mode query will also exist.
|
||||
const applicationModeQuery = await resolveEndpointsQuery(
|
||||
cliServer,
|
||||
language,
|
||||
Mode.Application,
|
||||
[],
|
||||
[],
|
||||
);
|
||||
|
||||
if (applicationModeQuery) {
|
||||
// Set up a synthetic pack so CodeQL doesn't crash later when we try
|
||||
// to resolve a query within this directory
|
||||
const syntheticQueryPack = {
|
||||
name: syntheticQueryPackName,
|
||||
version: "0.0.0",
|
||||
dependencies: {},
|
||||
};
|
||||
|
||||
const qlpackFile = join(queryDir, "codeql-pack.yml");
|
||||
await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8");
|
||||
} else {
|
||||
// If we can't resolve the query, we need to write them to desk ourselves.
|
||||
const externalApiQuerySuccess = await prepareExternalApiQuery(
|
||||
queryDir,
|
||||
language,
|
||||
// Resolve the query that we want to run.
|
||||
const query = fetchExternalApiQueries[language];
|
||||
if (!query) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
extLogger,
|
||||
telemetryListener,
|
||||
redactableError`No bundled model editor query found for language ${language}`,
|
||||
);
|
||||
if (!externalApiQuerySuccess) {
|
||||
return false;
|
||||
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");
|
||||
}
|
||||
|
||||
// Set up a synthetic pack so that the query can be resolved later.
|
||||
const syntheticQueryPack = {
|
||||
name: syntheticQueryPackName,
|
||||
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);
|
||||
}
|
||||
|
||||
// Download any other required packs
|
||||
if (language === "java" && modelConfig.llmGeneration) {
|
||||
await cliServer.packDownload([`codeql/${language}-automodel-queries`]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the query path to the model editor endpoints query. All queries are tagged like this:
|
||||
* modeleditor endpoints <mode>
|
||||
* Example: modeleditor endpoints framework-mode
|
||||
*
|
||||
* @param cliServer The CodeQL CLI server to use.
|
||||
* @param language The language of the query pack to use.
|
||||
* @param mode The mode to resolve the query for.
|
||||
* @param additionalPackNames Additional pack names to search.
|
||||
* @param additionalPackPaths Additional pack paths to search.
|
||||
*/
|
||||
export async function resolveEndpointsQuery(
|
||||
cliServer: CodeQLCliServer,
|
||||
language: string,
|
||||
mode: Mode,
|
||||
additionalPackNames: string[] = [],
|
||||
additionalPackPaths: string[] = [],
|
||||
): Promise<string | undefined> {
|
||||
const packsToSearch = [`codeql/${language}-queries`, ...additionalPackNames];
|
||||
export const externalApiQueriesProgressMaxStep = 2000;
|
||||
|
||||
// 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 resolveQueriesFromPacks(
|
||||
export async function runModelEditorQueries(
|
||||
mode: Mode,
|
||||
{
|
||||
cliServer,
|
||||
packsToSearch,
|
||||
{
|
||||
kind: "table",
|
||||
"tags contain all": ["modeleditor", "endpoints", modeTag(mode)],
|
||||
},
|
||||
additionalPackPaths,
|
||||
queryRunner,
|
||||
databaseItem,
|
||||
queryStorageDir,
|
||||
queryDir,
|
||||
progress,
|
||||
token,
|
||||
}: RunQueryOptions,
|
||||
): Promise<Method[] | 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.
|
||||
// For a reference of what this should do in the future, see the previous implementation in
|
||||
// https://github.com/github/vscode-codeql/blob/089d3566ef0bc67d9b7cc66e8fd6740b31c1c0b0/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts#L33-L72
|
||||
|
||||
progress({
|
||||
message: "Resolving QL packs",
|
||||
step: 1,
|
||||
maxStep: externalApiQueriesProgressMaxStep,
|
||||
});
|
||||
const additionalPacks = getOnDiskWorkspaceFolders();
|
||||
const extensionPacks = Object.keys(
|
||||
await cliServer.resolveQlpacks(additionalPacks, true),
|
||||
);
|
||||
if (queries.length > 1) {
|
||||
throw new Error(
|
||||
`Found multiple endpoints queries for ${mode}. Can't continue`,
|
||||
|
||||
progress({
|
||||
message: "Resolving query",
|
||||
step: 2,
|
||||
maxStep: externalApiQueriesProgressMaxStep,
|
||||
});
|
||||
|
||||
// Resolve the queries from either codeql/java-queries or from the temporary queryDir
|
||||
const queryPath = await resolveEndpointsQuery(
|
||||
cliServer,
|
||||
databaseItem.language,
|
||||
mode,
|
||||
[syntheticQueryPackName],
|
||||
[queryDir],
|
||||
);
|
||||
if (!queryPath) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
extLogger,
|
||||
telemetryListener,
|
||||
redactableError`The ${mode} model editor query could not be found. Try re-opening the model editor. If that doesn't work, try upgrading the CodeQL libraries.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (queries.length === 0) {
|
||||
// Run the actual query
|
||||
const completedQuery = await runQuery({
|
||||
queryRunner,
|
||||
databaseItem,
|
||||
queryPath,
|
||||
queryStorageDir,
|
||||
additionalPacks,
|
||||
extensionPacks,
|
||||
progress: (update) =>
|
||||
progress({
|
||||
step: update.step + 500,
|
||||
maxStep: externalApiQueriesProgressMaxStep,
|
||||
message: update.message,
|
||||
}),
|
||||
token,
|
||||
});
|
||||
|
||||
if (!completedQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the results and covert to internal representation
|
||||
progress({
|
||||
message: "Decoding results",
|
||||
step: 1600,
|
||||
maxStep: externalApiQueriesProgressMaxStep,
|
||||
});
|
||||
|
||||
const bqrsChunk = await readQueryResults({
|
||||
cliServer,
|
||||
bqrsPath: completedQuery.outputDir.bqrsPath,
|
||||
});
|
||||
if (!bqrsChunk) {
|
||||
return;
|
||||
}
|
||||
|
||||
progress({
|
||||
message: "Finalizing results",
|
||||
step: 1950,
|
||||
maxStep: externalApiQueriesProgressMaxStep,
|
||||
});
|
||||
|
||||
return decodeBqrsToMethods(bqrsChunk, mode);
|
||||
}
|
||||
|
||||
type GetResultsOptions = {
|
||||
cliServer: Pick<CodeQLCliServer, "bqrsInfo" | "bqrsDecode">;
|
||||
bqrsPath: string;
|
||||
};
|
||||
|
||||
export async function readQueryResults({
|
||||
cliServer,
|
||||
bqrsPath,
|
||||
}: GetResultsOptions) {
|
||||
const bqrsInfo = await cliServer.bqrsInfo(bqrsPath);
|
||||
if (bqrsInfo["result-sets"].length !== 1) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
extLogger,
|
||||
telemetryListener,
|
||||
redactableError`Expected exactly one result set, got ${bqrsInfo["result-sets"].length}`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return queries[0];
|
||||
const resultSet = bqrsInfo["result-sets"][0];
|
||||
|
||||
return cliServer.bqrsDecode(bqrsPath, resultSet.name);
|
||||
}
|
||||
|
||||
function queryNameFromMode(mode: Mode): string {
|
||||
return `${mode.charAt(0).toUpperCase() + mode.slice(1)}ModeEndpoints.ql`;
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ import { App } from "../common/app";
|
||||
import { redactableError } from "../common/errors";
|
||||
import {
|
||||
externalApiQueriesProgressMaxStep,
|
||||
runExternalApiQueries,
|
||||
} from "./external-api-usage-queries";
|
||||
runModelEditorQueries,
|
||||
} from "./model-editor-queries";
|
||||
import { Method } from "./method";
|
||||
import { ModeledMethod } from "./modeled-method";
|
||||
import { ExtensionPack } from "./shared/extension-pack";
|
||||
@@ -411,7 +411,7 @@ export class ModelEditorView extends AbstractWebview<
|
||||
|
||||
try {
|
||||
const cancellationTokenSource = new CancellationTokenSource();
|
||||
const queryResult = await runExternalApiQueries(mode, {
|
||||
const queryResult = await runModelEditorQueries(mode, {
|
||||
cliServer: this.cliServer,
|
||||
queryRunner: this.queryRunner,
|
||||
databaseItem: this.databaseItem,
|
||||
@@ -433,9 +433,9 @@ export class ModelEditorView extends AbstractWebview<
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
this.app.logger,
|
||||
this.app.telemetry,
|
||||
redactableError(
|
||||
asError(err),
|
||||
)`Failed to load external API usages: ${getErrorMessage(err)}`,
|
||||
redactableError(asError(err))`Failed to load results: ${getErrorMessage(
|
||||
err,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,18 @@ import { QueriesPanel } from "./queries-panel";
|
||||
import { QueryDiscovery } from "./query-discovery";
|
||||
import { QueryPackDiscovery } from "./query-pack-discovery";
|
||||
import { LanguageContextStore } from "../language-context-store";
|
||||
import { TreeViewSelectionChangeEvent } from "vscode";
|
||||
import { QueryTreeViewItem } from "./query-tree-view-item";
|
||||
|
||||
export class QueriesModule extends DisposableObject {
|
||||
private queriesPanel: QueriesPanel | undefined;
|
||||
private readonly onDidChangeSelectionEmitter = this.push(
|
||||
this.app.createEventEmitter<
|
||||
TreeViewSelectionChangeEvent<QueryTreeViewItem>
|
||||
>(),
|
||||
);
|
||||
|
||||
public readonly onDidChangeSelection = this.onDidChangeSelectionEmitter.event;
|
||||
|
||||
private constructor(readonly app: App) {
|
||||
super();
|
||||
@@ -52,6 +61,9 @@ export class QueriesModule extends DisposableObject {
|
||||
void queryDiscovery.initialRefresh();
|
||||
|
||||
this.queriesPanel = new QueriesPanel(queryDiscovery, app);
|
||||
this.queriesPanel.onDidChangeSelection((event) =>
|
||||
this.onDidChangeSelectionEmitter.fire(event),
|
||||
);
|
||||
this.push(this.queriesPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { DisposableObject } from "../common/disposable-object";
|
||||
import { QueryTreeDataProvider } from "./query-tree-data-provider";
|
||||
import { QueryDiscovery } from "./query-discovery";
|
||||
import { TextEditor, TreeView, window } from "vscode";
|
||||
import {
|
||||
Event,
|
||||
TextEditor,
|
||||
TreeView,
|
||||
TreeViewSelectionChangeEvent,
|
||||
window,
|
||||
} from "vscode";
|
||||
import { App } from "../common/app";
|
||||
import { QueryTreeViewItem } from "./query-tree-view-item";
|
||||
|
||||
@@ -16,6 +22,7 @@ export class QueriesPanel extends DisposableObject {
|
||||
super();
|
||||
|
||||
this.dataProvider = new QueryTreeDataProvider(queryDiscovery, app);
|
||||
this.push(this.dataProvider);
|
||||
|
||||
this.treeView = window.createTreeView("codeQLQueries", {
|
||||
treeDataProvider: this.dataProvider,
|
||||
@@ -25,6 +32,12 @@ export class QueriesPanel extends DisposableObject {
|
||||
this.subscribeToTreeSelectionEvents();
|
||||
}
|
||||
|
||||
public get onDidChangeSelection(): Event<
|
||||
TreeViewSelectionChangeEvent<QueryTreeViewItem>
|
||||
> {
|
||||
return this.treeView.onDidChangeSelection;
|
||||
}
|
||||
|
||||
private subscribeToTreeSelectionEvents(): void {
|
||||
// Keep track of whether the user has changed their text editor while
|
||||
// the tree view was not visible. If so, we will focus the text editor
|
||||
|
||||
@@ -389,6 +389,22 @@ WithCodeFlows.args = {
|
||||
message: { text: "id : String" },
|
||||
},
|
||||
},
|
||||
{
|
||||
location: {
|
||||
physicalLocation: {
|
||||
artifactLocation: {
|
||||
uri: "file:/",
|
||||
index: 5,
|
||||
},
|
||||
region: {
|
||||
startLine: 13,
|
||||
startColumn: 25,
|
||||
endColumn: 54,
|
||||
},
|
||||
},
|
||||
message: { text: "id : String" },
|
||||
},
|
||||
},
|
||||
{
|
||||
location: {
|
||||
physicalLocation: {
|
||||
|
||||
@@ -76,6 +76,36 @@ describe("parsing sarif", () => {
|
||||
).toEqual({
|
||||
hint: "artifact location has no uri",
|
||||
});
|
||||
expect(
|
||||
parseSarifLocation(
|
||||
{
|
||||
physicalLocation: {
|
||||
artifactLocation: {
|
||||
uri: "",
|
||||
index: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
"",
|
||||
),
|
||||
).toEqual({
|
||||
hint: "artifact location has empty uri",
|
||||
});
|
||||
expect(
|
||||
parseSarifLocation(
|
||||
{
|
||||
physicalLocation: {
|
||||
artifactLocation: {
|
||||
uri: "file:/",
|
||||
index: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
"",
|
||||
),
|
||||
).toEqual({
|
||||
hint: "artifact location has empty uri",
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse a sarif location with no region and no file protocol", () => {
|
||||
|
||||
@@ -13,9 +13,14 @@ import {
|
||||
WorkspaceFolder,
|
||||
} from "vscode";
|
||||
import { QlPackGenerator } from "../../../../src/local-queries/qlpack-generator";
|
||||
import * as workspaceFolders from "../../../../src/common/vscode/workspace-folders";
|
||||
import { createFileSync, ensureDirSync, removeSync } from "fs-extra";
|
||||
import { join } from "path";
|
||||
import {
|
||||
createFileSync,
|
||||
ensureDir,
|
||||
ensureDirSync,
|
||||
ensureFile,
|
||||
removeSync,
|
||||
} from "fs-extra";
|
||||
import { dirname, join } from "path";
|
||||
import { testCredentialsWithStub } from "../../../factories/authentication";
|
||||
import {
|
||||
DatabaseItem,
|
||||
@@ -29,6 +34,11 @@ import { Setting } from "../../../../src/config";
|
||||
import { QueryLanguage } from "../../../../src/common/query-language";
|
||||
import { App } from "../../../../src/common/app";
|
||||
import { createMockApp } from "../../../__mocks__/appMock";
|
||||
import {
|
||||
createQueryTreeFileItem,
|
||||
createQueryTreeFolderItem,
|
||||
QueryTreeViewItem,
|
||||
} from "../../../../src/queries-panel/query-tree-view-item";
|
||||
|
||||
describe("SkeletonQueryWizard", () => {
|
||||
let mockCli: CodeQLCliServer;
|
||||
@@ -60,6 +70,7 @@ describe("SkeletonQueryWizard", () => {
|
||||
|
||||
const credentials = testCredentialsWithStub();
|
||||
const chosenLanguage = "ruby";
|
||||
const selectedItems: QueryTreeViewItem[] = [];
|
||||
|
||||
beforeEach(async () => {
|
||||
mockCli = mockedObject<CodeQLCliServer>({
|
||||
@@ -130,6 +141,7 @@ describe("SkeletonQueryWizard", () => {
|
||||
mockApp,
|
||||
mockDatabaseManager,
|
||||
storagePath,
|
||||
selectedItems,
|
||||
);
|
||||
|
||||
askForGitHubRepoSpy = jest
|
||||
@@ -157,6 +169,7 @@ describe("SkeletonQueryWizard", () => {
|
||||
mockApp,
|
||||
mockDatabaseManager,
|
||||
storagePath,
|
||||
selectedItems,
|
||||
QueryLanguage.Swift,
|
||||
);
|
||||
});
|
||||
@@ -170,11 +183,6 @@ describe("SkeletonQueryWizard", () => {
|
||||
});
|
||||
|
||||
describe("if QL pack doesn't exist", () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(workspaceFolders, "isFolderAlreadyInWorkspace")
|
||||
.mockReturnValue(false);
|
||||
});
|
||||
it("should try to create a new QL pack based on the language", async () => {
|
||||
await wizard.execute();
|
||||
|
||||
@@ -223,10 +231,6 @@ describe("SkeletonQueryWizard", () => {
|
||||
|
||||
describe("if QL pack exists", () => {
|
||||
beforeEach(async () => {
|
||||
jest
|
||||
.spyOn(workspaceFolders, "isFolderAlreadyInWorkspace")
|
||||
.mockReturnValue(true);
|
||||
|
||||
// create a skeleton codeql-custom-queries-${language} folder
|
||||
// with an example QL file inside
|
||||
ensureDirSync(
|
||||
@@ -312,6 +316,7 @@ describe("SkeletonQueryWizard", () => {
|
||||
mockApp,
|
||||
mockDatabaseManagerWithItems,
|
||||
storagePath,
|
||||
selectedItems,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -361,6 +366,7 @@ describe("SkeletonQueryWizard", () => {
|
||||
mockApp,
|
||||
mockDatabaseManagerWithItems,
|
||||
storagePath,
|
||||
selectedItems,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -529,7 +535,7 @@ describe("SkeletonQueryWizard", () => {
|
||||
});
|
||||
|
||||
describe("determineStoragePath", () => {
|
||||
it("should prompt the user to provide a storage path", async () => {
|
||||
it("should prompt the user to provide a storage path when no items are selected", async () => {
|
||||
const chosenPath = await wizard.determineStoragePath();
|
||||
|
||||
expect(showInputBoxSpy).toHaveBeenCalledWith(
|
||||
@@ -538,10 +544,180 @@ describe("SkeletonQueryWizard", () => {
|
||||
expect(chosenPath).toEqual(storagePath);
|
||||
});
|
||||
|
||||
describe("with folders and files", () => {
|
||||
let queriesDir: tmp.DirResult;
|
||||
|
||||
beforeEach(async () => {
|
||||
queriesDir = tmp.dirSync({
|
||||
prefix: "queries_",
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
|
||||
await ensureDir(join(queriesDir.name, "folder"));
|
||||
await ensureFile(join(queriesDir.name, "queries-java", "example.ql"));
|
||||
await ensureFile(
|
||||
join(queriesDir.name, "codeql-custom-queries-swift", "example.ql"),
|
||||
);
|
||||
});
|
||||
|
||||
describe("with selected folder", () => {
|
||||
let selectedItems: QueryTreeViewItem[];
|
||||
|
||||
beforeEach(async () => {
|
||||
selectedItems = [
|
||||
createQueryTreeFolderItem(
|
||||
"folder",
|
||||
join(queriesDir.name, "folder"),
|
||||
[
|
||||
createQueryTreeFileItem(
|
||||
"example.ql",
|
||||
join(queriesDir.name, "folder", "example.ql"),
|
||||
"java",
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
wizard = new SkeletonQueryWizard(
|
||||
mockCli,
|
||||
jest.fn(),
|
||||
credentials,
|
||||
mockApp,
|
||||
mockDatabaseManager,
|
||||
storagePath,
|
||||
selectedItems,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns the selected folder path", async () => {
|
||||
const chosenPath = await wizard.determineStoragePath();
|
||||
|
||||
expect(chosenPath).toEqual(selectedItems[0].path);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with selected file", () => {
|
||||
let selectedItems: QueryTreeViewItem[];
|
||||
|
||||
beforeEach(async () => {
|
||||
selectedItems = [
|
||||
createQueryTreeFileItem(
|
||||
"example.ql",
|
||||
join(queriesDir.name, "queries-java", "example.ql"),
|
||||
"java",
|
||||
),
|
||||
];
|
||||
|
||||
wizard = new SkeletonQueryWizard(
|
||||
mockCli,
|
||||
jest.fn(),
|
||||
credentials,
|
||||
mockApp,
|
||||
mockDatabaseManager,
|
||||
storagePath,
|
||||
selectedItems,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns the selected file path", async () => {
|
||||
const chosenPath = await wizard.determineStoragePath();
|
||||
|
||||
expect(chosenPath).toEqual(dirname(selectedItems[0].path));
|
||||
});
|
||||
});
|
||||
|
||||
describe("with selected file with same name", () => {
|
||||
let selectedItems: QueryTreeViewItem[];
|
||||
|
||||
beforeEach(async () => {
|
||||
selectedItems = [
|
||||
createQueryTreeFileItem(
|
||||
"example.ql",
|
||||
join(
|
||||
queriesDir.name,
|
||||
"codeql-custom-queries-swift",
|
||||
"example.ql",
|
||||
),
|
||||
"java",
|
||||
),
|
||||
];
|
||||
|
||||
wizard = new SkeletonQueryWizard(
|
||||
mockCli,
|
||||
jest.fn(),
|
||||
credentials,
|
||||
mockApp,
|
||||
mockDatabaseManager,
|
||||
storagePath,
|
||||
selectedItems,
|
||||
QueryLanguage.Swift,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns the parent path", async () => {
|
||||
const chosenPath = await wizard.determineStoragePath();
|
||||
|
||||
expect(chosenPath).toEqual(queriesDir.name);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with multiple selected items", () => {
|
||||
let selectedItems: QueryTreeViewItem[];
|
||||
|
||||
beforeEach(async () => {
|
||||
selectedItems = [
|
||||
createQueryTreeFileItem(
|
||||
"example.ql",
|
||||
join(queriesDir.name, "queries-java", "example.ql"),
|
||||
"java",
|
||||
),
|
||||
createQueryTreeFolderItem(
|
||||
"folder",
|
||||
join(queriesDir.name, "folder"),
|
||||
[
|
||||
createQueryTreeFileItem(
|
||||
"example.ql",
|
||||
join(queriesDir.name, "folder", "example.ql"),
|
||||
"java",
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
wizard = new SkeletonQueryWizard(
|
||||
mockCli,
|
||||
jest.fn(),
|
||||
credentials,
|
||||
mockApp,
|
||||
mockDatabaseManager,
|
||||
storagePath,
|
||||
selectedItems,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns the first selected item path", async () => {
|
||||
const chosenPath = await wizard.determineStoragePath();
|
||||
|
||||
expect(chosenPath).toEqual(dirname(selectedItems[0].path));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("determineRootStoragePath", () => {
|
||||
it("should prompt the user to provide a storage path", async () => {
|
||||
const chosenPath = await wizard.determineRootStoragePath();
|
||||
|
||||
expect(showInputBoxSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ value: storagePath }),
|
||||
);
|
||||
expect(chosenPath).toEqual(storagePath);
|
||||
});
|
||||
|
||||
it("should write the chosen folder to settings", async () => {
|
||||
const updateValueSpy = jest.spyOn(Setting.prototype, "updateValue");
|
||||
|
||||
await wizard.determineStoragePath();
|
||||
await wizard.determineRootStoragePath();
|
||||
|
||||
expect(updateValueSpy).toHaveBeenCalledWith(storagePath, 2);
|
||||
});
|
||||
@@ -575,7 +751,7 @@ describe("SkeletonQueryWizard", () => {
|
||||
});
|
||||
|
||||
it("should not prompt the user", async () => {
|
||||
const chosenPath = await wizard.determineStoragePath();
|
||||
const chosenPath = await wizard.determineRootStoragePath();
|
||||
|
||||
expect(showInputBoxSpy).not.toHaveBeenCalled();
|
||||
expect(chosenPath).toEqual(storagePath);
|
||||
@@ -606,7 +782,7 @@ describe("SkeletonQueryWizard", () => {
|
||||
});
|
||||
|
||||
it("should return it and not prompt the user", async () => {
|
||||
const chosenPath = await wizard.determineStoragePath();
|
||||
const chosenPath = await wizard.determineRootStoragePath();
|
||||
|
||||
expect(showInputBoxSpy).not.toHaveBeenCalled();
|
||||
expect(chosenPath).toEqual(storedPath);
|
||||
@@ -635,7 +811,7 @@ describe("SkeletonQueryWizard", () => {
|
||||
});
|
||||
|
||||
it("should prompt the user for to provide a new folder name", async () => {
|
||||
const chosenPath = await wizard.determineStoragePath();
|
||||
const chosenPath = await wizard.determineRootStoragePath();
|
||||
|
||||
expect(showInputBoxSpy).toHaveBeenCalled();
|
||||
expect(chosenPath).toEqual(storagePath);
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
sourceLocationUri,
|
||||
} from "../../../factories/databases/databases";
|
||||
import { findSourceArchive } from "../../../../src/databases/local-databases/database-resolver";
|
||||
import { LanguageContextStore } from "../../../../src/language-context-store";
|
||||
|
||||
describe("local databases", () => {
|
||||
let databaseManager: DatabaseManager;
|
||||
@@ -84,9 +85,10 @@ describe("local databases", () => {
|
||||
},
|
||||
);
|
||||
|
||||
const mockApp = createMockApp({});
|
||||
databaseManager = new DatabaseManager(
|
||||
extensionContext,
|
||||
createMockApp({}),
|
||||
mockApp,
|
||||
mockedObject<QueryRunner>({
|
||||
registerDatabase: registerSpy,
|
||||
deregisterDatabase: deregisterSpy,
|
||||
@@ -98,6 +100,7 @@ describe("local databases", () => {
|
||||
resolveDatabase: resolveDatabaseSpy,
|
||||
packAdd: packAddSpy,
|
||||
}),
|
||||
new LanguageContextStore(mockApp),
|
||||
mockedObject<Logger>({
|
||||
log: logSpy,
|
||||
}),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
readQueryResults,
|
||||
runExternalApiQueries,
|
||||
} from "../../../../src/model-editor/external-api-usage-queries";
|
||||
runModelEditorQueries,
|
||||
} from "../../../../src/model-editor/model-editor-queries";
|
||||
import { createMockLogger } from "../../../__mocks__/loggerMock";
|
||||
import {
|
||||
DatabaseItem,
|
||||
@@ -21,274 +21,272 @@ import { CodeQLCliServer } from "../../../../src/codeql-cli/cli";
|
||||
import { QueryRunner } from "../../../../src/query-server";
|
||||
import { QueryOutputDir } from "../../../../src/run-queries-shared";
|
||||
|
||||
describe("external api usage query", () => {
|
||||
describe("runQuery", () => {
|
||||
const language = Object.keys(fetchExternalApiQueries)[
|
||||
Math.floor(Math.random() * Object.keys(fetchExternalApiQueries).length)
|
||||
] as QueryLanguage;
|
||||
describe("runModelEditorQueries", () => {
|
||||
const language = Object.keys(fetchExternalApiQueries)[
|
||||
Math.floor(Math.random() * Object.keys(fetchExternalApiQueries).length)
|
||||
] as QueryLanguage;
|
||||
|
||||
const queryDir = dirSync({ unsafeCleanup: true }).name;
|
||||
const queryDir = dirSync({ unsafeCleanup: true }).name;
|
||||
|
||||
it("should log an error", async () => {
|
||||
const showAndLogExceptionWithTelemetrySpy: jest.SpiedFunction<
|
||||
typeof showAndLogExceptionWithTelemetry
|
||||
> = jest.spyOn(log, "showAndLogExceptionWithTelemetry");
|
||||
it("should log an error", async () => {
|
||||
const showAndLogExceptionWithTelemetrySpy: jest.SpiedFunction<
|
||||
typeof showAndLogExceptionWithTelemetry
|
||||
> = jest.spyOn(log, "showAndLogExceptionWithTelemetry");
|
||||
|
||||
const outputDir = new QueryOutputDir(join((await file()).path, "1"));
|
||||
const outputDir = new QueryOutputDir(join((await file()).path, "1"));
|
||||
|
||||
const query = fetchExternalApiQueries[language];
|
||||
if (!query) {
|
||||
throw new Error(`No query found for language ${language}`);
|
||||
}
|
||||
const query = fetchExternalApiQueries[language];
|
||||
if (!query) {
|
||||
throw new Error(`No query found for language ${language}`);
|
||||
}
|
||||
|
||||
const options = {
|
||||
cliServer: mockedObject<CodeQLCliServer>({
|
||||
resolveQlpacks: jest.fn().mockResolvedValue({
|
||||
"my/extensions": "/a/b/c/",
|
||||
}),
|
||||
resolveQueriesInSuite: jest
|
||||
.fn()
|
||||
.mockResolvedValue(["/a/b/c/ApplicationModeEndpoints.ql"]),
|
||||
packPacklist: jest
|
||||
.fn()
|
||||
.mockResolvedValue([
|
||||
"/a/b/c/qlpack.yml",
|
||||
"/a/b/c/qlpack.lock.yml",
|
||||
"/a/b/c/qlpack2.yml",
|
||||
]),
|
||||
}),
|
||||
queryRunner: mockedObject<QueryRunner>({
|
||||
createQueryRun: jest.fn().mockReturnValue({
|
||||
evaluate: jest.fn().mockResolvedValue({
|
||||
resultType: QueryResultType.CANCELLATION,
|
||||
}),
|
||||
outputDir,
|
||||
}),
|
||||
logger: createMockLogger(),
|
||||
}),
|
||||
databaseItem: mockedObject<DatabaseItem>({
|
||||
databaseUri: mockedUri("/a/b/c/src.zip"),
|
||||
contents: {
|
||||
kind: DatabaseKind.Database,
|
||||
name: "foo",
|
||||
datasetUri: mockedUri(),
|
||||
},
|
||||
language,
|
||||
}),
|
||||
queryStorageDir: "/tmp/queries",
|
||||
queryDir,
|
||||
progress: jest.fn(),
|
||||
token: {
|
||||
isCancellationRequested: false,
|
||||
onCancellationRequested: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
await runExternalApiQueries(Mode.Application, options),
|
||||
).toBeUndefined();
|
||||
expect(showAndLogExceptionWithTelemetrySpy).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
undefined,
|
||||
expect.any(RedactableError),
|
||||
);
|
||||
});
|
||||
|
||||
it("should run query for random language", async () => {
|
||||
const outputDir = new QueryOutputDir(join((await file()).path, "1"));
|
||||
|
||||
const query = fetchExternalApiQueries[language];
|
||||
if (!query) {
|
||||
throw new Error(`No query found for language ${language}`);
|
||||
}
|
||||
|
||||
const options = {
|
||||
cliServer: mockedObject<CodeQLCliServer>({
|
||||
resolveQlpacks: jest.fn().mockResolvedValue({
|
||||
"my/extensions": "/a/b/c/",
|
||||
}),
|
||||
resolveQueriesInSuite: jest
|
||||
.fn()
|
||||
.mockResolvedValue(["/a/b/c/ApplicationModeEndpoints.ql"]),
|
||||
packPacklist: jest
|
||||
.fn()
|
||||
.mockResolvedValue([
|
||||
"/a/b/c/qlpack.yml",
|
||||
"/a/b/c/qlpack.lock.yml",
|
||||
"/a/b/c/qlpack2.yml",
|
||||
]),
|
||||
bqrsInfo: jest.fn().mockResolvedValue({
|
||||
"result-sets": [],
|
||||
}),
|
||||
}),
|
||||
queryRunner: mockedObject<QueryRunner>({
|
||||
createQueryRun: jest.fn().mockReturnValue({
|
||||
evaluate: jest.fn().mockResolvedValue({
|
||||
resultType: QueryResultType.SUCCESS,
|
||||
outputDir,
|
||||
}),
|
||||
outputDir,
|
||||
}),
|
||||
logger: createMockLogger(),
|
||||
}),
|
||||
databaseItem: mockedObject<DatabaseItem>({
|
||||
databaseUri: mockedUri("/a/b/c/src.zip"),
|
||||
contents: {
|
||||
kind: DatabaseKind.Database,
|
||||
name: "foo",
|
||||
datasetUri: mockedUri(),
|
||||
},
|
||||
language,
|
||||
}),
|
||||
queryStorageDir: "/tmp/queries",
|
||||
queryDir,
|
||||
progress: jest.fn(),
|
||||
token: {
|
||||
isCancellationRequested: false,
|
||||
onCancellationRequested: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
const result = await runExternalApiQueries(Mode.Framework, options);
|
||||
|
||||
expect(result).not.toBeUndefined;
|
||||
|
||||
expect(options.cliServer.resolveQlpacks).toHaveBeenCalledTimes(1);
|
||||
expect(options.cliServer.resolveQlpacks).toHaveBeenCalledWith([], true);
|
||||
expect(options.queryRunner.createQueryRun).toHaveBeenCalledWith(
|
||||
"/a/b/c/src.zip",
|
||||
{
|
||||
queryPath: expect.stringMatching(/\S*ModeEndpoints\.ql/),
|
||||
quickEvalPosition: undefined,
|
||||
quickEvalCountOnly: false,
|
||||
},
|
||||
false,
|
||||
[],
|
||||
["my/extensions"],
|
||||
{},
|
||||
"/tmp/queries",
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("readQueryResults", () => {
|
||||
const options = {
|
||||
cliServer: {
|
||||
bqrsInfo: jest.fn(),
|
||||
bqrsDecode: jest.fn(),
|
||||
cliServer: mockedObject<CodeQLCliServer>({
|
||||
resolveQlpacks: jest.fn().mockResolvedValue({
|
||||
"my/extensions": "/a/b/c/",
|
||||
}),
|
||||
resolveQueriesInSuite: jest
|
||||
.fn()
|
||||
.mockResolvedValue(["/a/b/c/ApplicationModeEndpoints.ql"]),
|
||||
packPacklist: jest
|
||||
.fn()
|
||||
.mockResolvedValue([
|
||||
"/a/b/c/qlpack.yml",
|
||||
"/a/b/c/qlpack.lock.yml",
|
||||
"/a/b/c/qlpack2.yml",
|
||||
]),
|
||||
}),
|
||||
queryRunner: mockedObject<QueryRunner>({
|
||||
createQueryRun: jest.fn().mockReturnValue({
|
||||
evaluate: jest.fn().mockResolvedValue({
|
||||
resultType: QueryResultType.CANCELLATION,
|
||||
}),
|
||||
outputDir,
|
||||
}),
|
||||
logger: createMockLogger(),
|
||||
}),
|
||||
databaseItem: mockedObject<DatabaseItem>({
|
||||
databaseUri: mockedUri("/a/b/c/src.zip"),
|
||||
contents: {
|
||||
kind: DatabaseKind.Database,
|
||||
name: "foo",
|
||||
datasetUri: mockedUri(),
|
||||
},
|
||||
language,
|
||||
}),
|
||||
queryStorageDir: "/tmp/queries",
|
||||
queryDir,
|
||||
progress: jest.fn(),
|
||||
token: {
|
||||
isCancellationRequested: false,
|
||||
onCancellationRequested: jest.fn(),
|
||||
},
|
||||
bqrsPath: "/tmp/results.bqrs",
|
||||
};
|
||||
|
||||
let showAndLogExceptionWithTelemetrySpy: jest.SpiedFunction<
|
||||
typeof showAndLogExceptionWithTelemetry
|
||||
>;
|
||||
expect(
|
||||
await runModelEditorQueries(Mode.Application, options),
|
||||
).toBeUndefined();
|
||||
expect(showAndLogExceptionWithTelemetrySpy).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
undefined,
|
||||
expect.any(RedactableError),
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
showAndLogExceptionWithTelemetrySpy = jest.spyOn(
|
||||
log,
|
||||
"showAndLogExceptionWithTelemetry",
|
||||
);
|
||||
});
|
||||
it("should run query for random language", async () => {
|
||||
const outputDir = new QueryOutputDir(join((await file()).path, "1"));
|
||||
|
||||
it("returns undefined when there are no results", async () => {
|
||||
options.cliServer.bqrsInfo.mockResolvedValue({
|
||||
"result-sets": [],
|
||||
});
|
||||
const query = fetchExternalApiQueries[language];
|
||||
if (!query) {
|
||||
throw new Error(`No query found for language ${language}`);
|
||||
}
|
||||
|
||||
expect(await readQueryResults(options)).toBeUndefined();
|
||||
expect(showAndLogExceptionWithTelemetrySpy).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
undefined,
|
||||
expect.any(RedactableError),
|
||||
);
|
||||
});
|
||||
const options = {
|
||||
cliServer: mockedObject<CodeQLCliServer>({
|
||||
resolveQlpacks: jest.fn().mockResolvedValue({
|
||||
"my/extensions": "/a/b/c/",
|
||||
}),
|
||||
resolveQueriesInSuite: jest
|
||||
.fn()
|
||||
.mockResolvedValue(["/a/b/c/ApplicationModeEndpoints.ql"]),
|
||||
packPacklist: jest
|
||||
.fn()
|
||||
.mockResolvedValue([
|
||||
"/a/b/c/qlpack.yml",
|
||||
"/a/b/c/qlpack.lock.yml",
|
||||
"/a/b/c/qlpack2.yml",
|
||||
]),
|
||||
bqrsInfo: jest.fn().mockResolvedValue({
|
||||
"result-sets": [],
|
||||
}),
|
||||
}),
|
||||
queryRunner: mockedObject<QueryRunner>({
|
||||
createQueryRun: jest.fn().mockReturnValue({
|
||||
evaluate: jest.fn().mockResolvedValue({
|
||||
resultType: QueryResultType.SUCCESS,
|
||||
outputDir,
|
||||
}),
|
||||
outputDir,
|
||||
}),
|
||||
logger: createMockLogger(),
|
||||
}),
|
||||
databaseItem: mockedObject<DatabaseItem>({
|
||||
databaseUri: mockedUri("/a/b/c/src.zip"),
|
||||
contents: {
|
||||
kind: DatabaseKind.Database,
|
||||
name: "foo",
|
||||
datasetUri: mockedUri(),
|
||||
},
|
||||
language,
|
||||
}),
|
||||
queryStorageDir: "/tmp/queries",
|
||||
queryDir,
|
||||
progress: jest.fn(),
|
||||
token: {
|
||||
isCancellationRequested: false,
|
||||
onCancellationRequested: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
it("returns undefined when there are multiple result sets", async () => {
|
||||
options.cliServer.bqrsInfo.mockResolvedValue({
|
||||
"result-sets": [
|
||||
{
|
||||
name: "#select",
|
||||
rows: 10,
|
||||
columns: [
|
||||
{ name: "usage", kind: "e" },
|
||||
{ name: "apiName", kind: "s" },
|
||||
{ kind: "s" },
|
||||
{ kind: "s" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "#select2",
|
||||
rows: 10,
|
||||
columns: [
|
||||
{ name: "usage", kind: "e" },
|
||||
{ name: "apiName", kind: "s" },
|
||||
{ kind: "s" },
|
||||
{ kind: "s" },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = await runModelEditorQueries(Mode.Framework, options);
|
||||
|
||||
expect(await readQueryResults(options)).toBeUndefined();
|
||||
expect(showAndLogExceptionWithTelemetrySpy).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
undefined,
|
||||
expect.any(RedactableError),
|
||||
);
|
||||
});
|
||||
expect(result).not.toBeUndefined;
|
||||
|
||||
it("gets the result set", async () => {
|
||||
options.cliServer.bqrsInfo.mockResolvedValue({
|
||||
"result-sets": [
|
||||
{
|
||||
name: "#select",
|
||||
rows: 10,
|
||||
columns: [
|
||||
{ name: "usage", kind: "e" },
|
||||
{ name: "apiName", kind: "s" },
|
||||
{ kind: "s" },
|
||||
{ kind: "s" },
|
||||
],
|
||||
},
|
||||
],
|
||||
"compatible-query-kinds": ["Table", "Tree", "Graph"],
|
||||
});
|
||||
const decodedResultSet = {
|
||||
columns: [
|
||||
{ name: "usage", kind: "e" },
|
||||
{ name: "apiName", kind: "s" },
|
||||
{ kind: "s" },
|
||||
{ kind: "s" },
|
||||
],
|
||||
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,
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
options.cliServer.bqrsDecode.mockResolvedValue(decodedResultSet);
|
||||
|
||||
const result = await readQueryResults(options);
|
||||
expect(result).toEqual(decodedResultSet);
|
||||
expect(options.cliServer.bqrsInfo).toHaveBeenCalledWith(options.bqrsPath);
|
||||
expect(options.cliServer.bqrsDecode).toHaveBeenCalledWith(
|
||||
options.bqrsPath,
|
||||
"#select",
|
||||
);
|
||||
});
|
||||
expect(options.cliServer.resolveQlpacks).toHaveBeenCalledTimes(1);
|
||||
expect(options.cliServer.resolveQlpacks).toHaveBeenCalledWith([], true);
|
||||
expect(options.queryRunner.createQueryRun).toHaveBeenCalledWith(
|
||||
"/a/b/c/src.zip",
|
||||
{
|
||||
queryPath: expect.stringMatching(/\S*ModeEndpoints\.ql/),
|
||||
quickEvalPosition: undefined,
|
||||
quickEvalCountOnly: false,
|
||||
},
|
||||
false,
|
||||
[],
|
||||
["my/extensions"],
|
||||
{},
|
||||
"/tmp/queries",
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("readQueryResults", () => {
|
||||
const options = {
|
||||
cliServer: {
|
||||
bqrsInfo: jest.fn(),
|
||||
bqrsDecode: jest.fn(),
|
||||
},
|
||||
bqrsPath: "/tmp/results.bqrs",
|
||||
};
|
||||
|
||||
let showAndLogExceptionWithTelemetrySpy: jest.SpiedFunction<
|
||||
typeof showAndLogExceptionWithTelemetry
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
showAndLogExceptionWithTelemetrySpy = jest.spyOn(
|
||||
log,
|
||||
"showAndLogExceptionWithTelemetry",
|
||||
);
|
||||
});
|
||||
|
||||
it("returns undefined when there are no results", async () => {
|
||||
options.cliServer.bqrsInfo.mockResolvedValue({
|
||||
"result-sets": [],
|
||||
});
|
||||
|
||||
expect(await readQueryResults(options)).toBeUndefined();
|
||||
expect(showAndLogExceptionWithTelemetrySpy).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
undefined,
|
||||
expect.any(RedactableError),
|
||||
);
|
||||
});
|
||||
|
||||
it("returns undefined when there are multiple result sets", async () => {
|
||||
options.cliServer.bqrsInfo.mockResolvedValue({
|
||||
"result-sets": [
|
||||
{
|
||||
name: "#select",
|
||||
rows: 10,
|
||||
columns: [
|
||||
{ name: "usage", kind: "e" },
|
||||
{ name: "apiName", kind: "s" },
|
||||
{ kind: "s" },
|
||||
{ kind: "s" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "#select2",
|
||||
rows: 10,
|
||||
columns: [
|
||||
{ name: "usage", kind: "e" },
|
||||
{ name: "apiName", kind: "s" },
|
||||
{ kind: "s" },
|
||||
{ kind: "s" },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(await readQueryResults(options)).toBeUndefined();
|
||||
expect(showAndLogExceptionWithTelemetrySpy).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
undefined,
|
||||
expect.any(RedactableError),
|
||||
);
|
||||
});
|
||||
|
||||
it("gets the result set", async () => {
|
||||
options.cliServer.bqrsInfo.mockResolvedValue({
|
||||
"result-sets": [
|
||||
{
|
||||
name: "#select",
|
||||
rows: 10,
|
||||
columns: [
|
||||
{ name: "usage", kind: "e" },
|
||||
{ name: "apiName", kind: "s" },
|
||||
{ kind: "s" },
|
||||
{ kind: "s" },
|
||||
],
|
||||
},
|
||||
],
|
||||
"compatible-query-kinds": ["Table", "Tree", "Graph"],
|
||||
});
|
||||
const decodedResultSet = {
|
||||
columns: [
|
||||
{ name: "usage", kind: "e" },
|
||||
{ name: "apiName", kind: "s" },
|
||||
{ kind: "s" },
|
||||
{ kind: "s" },
|
||||
],
|
||||
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,
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
options.cliServer.bqrsDecode.mockResolvedValue(decodedResultSet);
|
||||
|
||||
const result = await readQueryResults(options);
|
||||
expect(result).toEqual(decodedResultSet);
|
||||
expect(options.cliServer.bqrsInfo).toHaveBeenCalledWith(options.bqrsPath);
|
||||
expect(options.cliServer.bqrsDecode).toHaveBeenCalledWith(
|
||||
options.bqrsPath,
|
||||
"#select",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { readFile, readFileSync, readdir } from "fs-extra";
|
||||
import { join } from "path";
|
||||
import { load } from "js-yaml";
|
||||
import { setUpPack } from "../../../../src/model-editor/model-editor-queries";
|
||||
import { setUpPack } from "../../../../src/model-editor/model-editor-queries-setup";
|
||||
import { dirSync } from "tmp-promise";
|
||||
import { fetchExternalApiQueries } from "../../../../src/model-editor/queries";
|
||||
import { QueryLanguage } from "../../../../src/common/query-language";
|
||||
@@ -57,7 +57,7 @@ describe("setUpPack", () => {
|
||||
);
|
||||
const suiteYaml = load(suiteFileContents);
|
||||
expect(suiteYaml).toEqual({
|
||||
name: "codeql/external-api-usage",
|
||||
name: "codeql/model-editor-queries",
|
||||
version: "0.0.0",
|
||||
dependencies: {
|
||||
[`codeql/${language}-all`]: "*",
|
||||
@@ -108,7 +108,7 @@ describe("setUpPack", () => {
|
||||
);
|
||||
const suiteYaml = load(suiteFileContents);
|
||||
expect(suiteYaml).toEqual({
|
||||
name: "codeql/external-api-usage",
|
||||
name: "codeql/model-editor-queries",
|
||||
version: "0.0.0",
|
||||
dependencies: {},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user