Merge remote-tracking branch 'origin/main' into koesie10/find-github-repository

This commit is contained in:
Koen Vlaswinkel
2023-11-16 16:49:35 +01:00
45 changed files with 479 additions and 191 deletions

View File

@@ -2,6 +2,15 @@
## [UNRELEASED]
- Add new CodeQL views for managing databases and queries:
1. A queries panel, to create and run queries in one place.
2. A language selector, to quickly determine the language compatibility of databases and queries.
For more information, see the [documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/analyzing-your-projects/#filtering-databases-and-queries-by-language).
- When adding a CodeQL database, we no longer add the database source folder to the workspace by default (since this caused bugs in single-folder workspaces). [#3047](https://github.com/github/vscode-codeql/pull/3047)
- You can manually add individual database source folders to the workspace with the "Add Database Source to Workspace" right-click command in the databases view.
- To restore the old behavior of adding all database source folders by default, set the `codeQL.addingDatabases.addDatabaseSourceToWorkspace` setting to `true`.
- Rename the `codeQL.databaseDownload.allowHttp` setting to `codeQL.addingDatabases.allowHttp`, so that database-related settings are grouped together in the Settings UI. [#3047](https://github.com/github/vscode-codeql/pull/3047) & [#3069](https://github.com/github/vscode-codeql/pull/3069)
- The "Sort by Language" action in the databases view now sorts by name within each language. [#3055](https://github.com/github/vscode-codeql/pull/3055)
## 1.9.4 - 6 November 2023

View File

@@ -4392,9 +4392,9 @@
}
},
"node_modules/@octokit/openapi-types": {
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.0.0.tgz",
"integrity": "sha512-PclQ6JGMTE9iUStpzMkwLCISFn/wDeRjkZFIKALpvJQNBGwDoYYi2fFvuHwssoQ1rXI5mfh6jgTgWuddeUzfWw=="
"version": "19.0.2",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.0.2.tgz",
"integrity": "sha512-8li32fUDUeml/ACRp/njCWTsk5t17cfTM1jp9n08pBrqs5cDFJubtjsSnuz56r5Tad6jdEPJld7LxNp9dNcyjQ=="
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "9.0.0",
@@ -35269,9 +35269,9 @@
}
},
"@octokit/openapi-types": {
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.0.0.tgz",
"integrity": "sha512-PclQ6JGMTE9iUStpzMkwLCISFn/wDeRjkZFIKALpvJQNBGwDoYYi2fFvuHwssoQ1rXI5mfh6jgTgWuddeUzfWw=="
"version": "19.0.2",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.0.2.tgz",
"integrity": "sha512-8li32fUDUeml/ACRp/njCWTsk5t17cfTM1jp9n08pBrqs5cDFJubtjsSnuz56r5Tad6jdEPJld7LxNp9dNcyjQ=="
},
"@octokit/plugin-paginate-rest": {
"version": "9.0.0",

View File

@@ -374,13 +374,23 @@
},
{
"type": "object",
"title": "Downloading databases",
"title": "Adding databases",
"order": 6,
"properties": {
"codeQL.databaseDownload.allowHttp": {
"codeQL.addingDatabases.allowHttp": {
"type": "boolean",
"default": false,
"description": "Allow database to be downloaded via HTTP. Warning: enabling this option will allow downloading from insecure servers."
"description": "Allow databases to be downloaded via HTTP. Warning: enabling this option will allow downloading from insecure servers."
},
"codeQL.databaseDownload.allowHttp": {
"type": "boolean",
"markdownDeprecationMessage": "**Deprecated**: Please use `#codeQL.addingDatabases.allowHttp#` instead.",
"deprecationMessage": "Deprecated: Please use codeQL.addingDatabases.allowHttp instead."
},
"codeQL.addingDatabases.addDatabaseSourceToWorkspace": {
"type": "boolean",
"default": false,
"markdownDescription": "When adding a CodeQL database, automatically add the database's source folder as a workspace folder. Warning: enabling this option in a single-folder workspace will cause the workspace to reload as a [multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces). This may cause query history and database lists to be reset."
}
}
},
@@ -1678,10 +1688,6 @@
"command": "codeQL.mockGitHubApiServer.unloadScenario",
"when": "config.codeQL.mockGitHubApiServer.enabled && codeQL.mockGitHubApiServer.scenarioLoaded"
},
{
"command": "codeQL.createQuery",
"when": "config.codeQL.codespacesTemplate || config.codeQL.canary && config.codeQL.queriesPanel"
},
{
"command": "codeQLTests.acceptOutputContextTestItem",
"when": "false"
@@ -1766,8 +1772,7 @@
"ql-container": [
{
"id": "codeQLLanguageSelection",
"name": "Language",
"when": "config.codeQL.canary && config.codeQL.showLanguageFilter"
"name": "Language"
},
{
"id": "codeQLDatabases",
@@ -1775,8 +1780,7 @@
},
{
"id": "codeQLQueries",
"name": "Queries",
"when": "config.codeQL.canary && config.codeQL.queriesPanel"
"name": "Queries"
},
{
"id": "codeQLVariantAnalysisRepositories",

View File

@@ -641,12 +641,32 @@ export function isCodespacesTemplate() {
return !!CODESPACES_TEMPLATE.getValue<boolean>();
}
// Deprecated after v1.9.4. Can be removed in a few versions.
const DATABASE_DOWNLOAD_SETTING = new Setting("databaseDownload", ROOT_SETTING);
const DEPRECATED_ALLOW_HTTP_SETTING = new Setting(
"allowHttp",
DATABASE_DOWNLOAD_SETTING,
);
const ALLOW_HTTP_SETTING = new Setting("allowHttp", DATABASE_DOWNLOAD_SETTING);
const ADDING_DATABASES_SETTING = new Setting("addingDatabases", ROOT_SETTING);
const ALLOW_HTTP_SETTING = new Setting("allowHttp", ADDING_DATABASES_SETTING);
export function allowHttp(): boolean {
return ALLOW_HTTP_SETTING.getValue<boolean>() || false;
return (
ALLOW_HTTP_SETTING.getValue<boolean>() ||
DEPRECATED_ALLOW_HTTP_SETTING.getValue<boolean>() ||
false
);
}
const ADD_DATABASE_SOURCE_TO_WORKSPACE_SETTING = new Setting(
"addDatabaseSourceToWorkspace",
ADDING_DATABASES_SETTING,
);
export function addDatabaseSourceToWorkspace(): boolean {
return ADD_DATABASE_SOURCE_TO_WORKSPACE_SETTING.getValue<boolean>() || false;
}
/**
@@ -690,15 +710,6 @@ export async function setAutogenerateQlPacks(choice: AutogenerateQLPacks) {
);
}
/**
* A flag indicating whether to show the queries panel in the QL view container.
*/
const QUERIES_PANEL = new Setting("queriesPanel", ROOT_SETTING);
export function showQueriesPanel(): boolean {
return !!QUERIES_PANEL.getValue<boolean>();
}
const MODEL_SETTING = new Setting("model", ROOT_SETTING);
const FLOW_GENERATION = new Setting("flowGeneration", MODEL_SETTING);
const LLM_GENERATION = new Setting("llmGeneration", MODEL_SETTING);

View File

@@ -1,6 +1,8 @@
// Contains models and consts for the data we want to store in the database config.
// Changes to these models should be done carefully and account for backwards compatibility of data.
import { DatabaseOrigin } from "../local-databases/database-origin";
export const DB_CONFIG_VERSION = 1;
export interface DbConfig {
@@ -88,6 +90,7 @@ export interface LocalDatabase {
name: string;
dateAdded: number;
language: string;
origin: DatabaseOrigin;
storagePath: string;
}

View File

@@ -29,10 +29,11 @@ import {
} from "../common/github-url-identifier-helper";
import { Credentials } from "../common/authentication";
import { AppCommandManager } from "../common/commands";
import { allowHttp } from "../config";
import { addDatabaseSourceToWorkspace, allowHttp } from "../config";
import { showAndLogInformationMessage } from "../common/logging";
import { AppOctokit } from "../common/octokit";
import { getLanguageDisplayName } from "../common/query-language";
import { DatabaseOrigin } from "./local-databases/database-origin";
/**
* Prompts a user to fetch a database from a remote location. Database is assumed to be an archive file.
@@ -62,6 +63,10 @@ export async function promptImportInternetDatabase(
databaseManager,
storagePath,
undefined,
{
type: "url",
url: databaseUrl,
},
progress,
cli,
);
@@ -99,7 +104,7 @@ export async function promptImportGithubDatabase(
cli?: CodeQLCliServer,
language?: string,
makeSelected = true,
addSourceArchiveFolder = true,
addSourceArchiveFolder = addDatabaseSourceToWorkspace(),
): Promise<DatabaseItem | undefined> {
const githubRepo = await askForGitHubRepo(progress);
if (!githubRepo) {
@@ -178,7 +183,7 @@ export async function downloadGitHubDatabase(
cli?: CodeQLCliServer,
language?: string,
makeSelected = true,
addSourceArchiveFolder = true,
addSourceArchiveFolder = addDatabaseSourceToWorkspace(),
): Promise<DatabaseItem | undefined> {
const nwo = getNwoFromGitHubUrl(githubRepo) || githubRepo;
if (!isValidGitHubNwo(nwo)) {
@@ -199,7 +204,8 @@ export async function downloadGitHubDatabase(
return;
}
const { databaseUrl, name, owner } = result;
const { databaseUrl, name, owner, databaseId, databaseCreatedAt, commitOid } =
result;
/**
* The 'token' property of the token object returned by `octokit.auth()`.
@@ -221,6 +227,13 @@ export async function downloadGitHubDatabase(
databaseManager,
storagePath,
`${owner}/${name}`,
{
type: "github",
repository: nwo,
databaseId,
databaseCreatedAt,
commitOid,
},
progress,
cli,
makeSelected,
@@ -250,6 +263,10 @@ export async function importArchiveDatabase(
databaseManager,
storagePath,
undefined,
{
type: "archive",
path: databaseUrl,
},
progress,
cli,
);
@@ -282,6 +299,7 @@ export async function importArchiveDatabase(
* @param databaseManager the DatabaseManager
* @param storagePath where to store the unzipped database.
* @param nameOverride a name for the database that overrides the default
* @param origin the origin of the database
* @param progress callback to send progress messages to
* @param makeSelected make the new database selected in the databases panel (default: true)
* @param addSourceArchiveFolder whether to add a workspace folder containing the source archive to the workspace
@@ -292,10 +310,11 @@ async function databaseArchiveFetcher(
databaseManager: DatabaseManager,
storagePath: string,
nameOverride: string | undefined,
origin: DatabaseOrigin,
progress: ProgressCallback,
cli?: CodeQLCliServer,
makeSelected = true,
addSourceArchiveFolder = true,
addSourceArchiveFolder = addDatabaseSourceToWorkspace(),
): Promise<DatabaseItem> {
progress({
message: "Getting database",
@@ -336,6 +355,7 @@ async function databaseArchiveFetcher(
const item = await databaseManager.openDatabase(
Uri.file(dbPath),
origin,
makeSelected,
nameOverride,
{
@@ -476,7 +496,7 @@ async function checkForFailingResponse(
return response;
}
// An error downloading the database. Attempt to extract the resaon behind it.
// An error downloading the database. Attempt to extract the reason behind it.
const text = await response.text();
let msg: string;
try {
@@ -533,16 +553,19 @@ export async function convertGithubNwoToDatabaseUrl(
databaseUrl: string;
owner: string;
name: string;
databaseId: number;
databaseCreatedAt: string;
commitOid: string | null;
}
| undefined
> {
try {
const [owner, repo] = nwo.split("/");
const response = await octokit.request(
"GET /repos/:owner/:repo/code-scanning/codeql/databases",
{ owner, repo },
);
const response = await octokit.rest.codeScanning.listCodeqlDatabases({
owner,
repo,
});
const languages = response.data.map((db: any) => db.language);
@@ -553,10 +576,20 @@ export async function convertGithubNwoToDatabaseUrl(
}
}
const databaseForLanguage = response.data.find(
(db: any) => db.language === language,
);
if (!databaseForLanguage) {
throw new Error(`No database found for language '${language}'`);
}
return {
databaseUrl: `https://api.github.com/repos/${owner}/${repo}/code-scanning/codeql/databases/${language}`,
databaseUrl: databaseForLanguage.url,
owner,
name: repo,
databaseId: databaseForLanguage.id,
databaseCreatedAt: databaseForLanguage.created_at,
commitOid: databaseForLanguage.commit_oid ?? null,
};
} catch (e) {
void extLogger.log(`Error: ${getErrorMessage(e)}`);

View File

@@ -1,5 +1,7 @@
// This file contains models that are used to represent the databases.
import { DatabaseOrigin } from "./local-databases/database-origin";
export enum DbItemKind {
RootLocal = "RootLocal",
LocalList = "LocalList",
@@ -38,6 +40,7 @@ export interface LocalDatabaseDbItem {
databaseName: string;
dateAdded: number;
language: string;
origin: DatabaseOrigin;
storagePath: string;
parentListName?: string;
}

View File

@@ -197,6 +197,7 @@ function createLocalDb(
databaseName: db.name,
dateAdded: db.dateAdded,
language: db.language,
origin: db.origin,
storagePath: db.storagePath,
selected: !!selected,
parentListName: listName,

View File

@@ -367,6 +367,9 @@ export class DatabaseUI extends DisposableObject {
await this.databaseManager.openDatabase(
uri,
{
type: "folder",
},
makeSelected,
nameOverride,
{
@@ -704,7 +707,9 @@ export class DatabaseUI extends DisposableObject {
this.queryServer?.cliServer,
);
} else {
await this.databaseManager.openDatabase(uri);
await this.databaseManager.openDatabase(uri, {
type: "folder",
});
}
} catch (e) {
// rethrow and let this be handled by default error handling.
@@ -819,7 +824,9 @@ export class DatabaseUI extends DisposableObject {
if (byFolder) {
const fixedUri = await this.fixDbUri(uri);
// we are selecting a database folder
return await this.databaseManager.openDatabase(fixedUri);
return await this.databaseManager.openDatabase(fixedUri, {
type: "folder",
});
} else {
// we are selecting a database archive. Must unzip into a workspace-controlled area
// before importing.

View File

@@ -14,6 +14,7 @@ import { isLikelyDatabaseRoot } from "./db-contents-heuristics";
import { stat } from "fs-extra";
import { containsPath, pathsEqual } from "../../common/files";
import { DatabaseContents } from "./database-contents";
import { DatabaseOrigin } from "./database-origin";
export class DatabaseItemImpl implements DatabaseItem {
// These are only public in the implementation, they are readonly in the interface
@@ -61,6 +62,10 @@ export class DatabaseItemImpl implements DatabaseItem {
return this.options.dateAdded;
}
public get origin(): DatabaseOrigin | undefined {
return this.options.origin;
}
public resolveSourceFile(uriStr: string | undefined): vscode.Uri {
const sourceArchive = this.sourceArchive;
const uri = uriStr ? vscode.Uri.parse(uriStr, true) : undefined;

View File

@@ -2,6 +2,7 @@ import vscode from "vscode";
import * as cli from "../../codeql-cli/cli";
import { DatabaseContents } from "./database-contents";
import { DatabaseOptions } from "./database-options";
import { DatabaseOrigin } from "./database-origin";
/** An item in the list of available databases */
export interface DatabaseItem {
@@ -25,6 +26,11 @@ export interface DatabaseItem {
*/
readonly dateAdded: number | undefined;
/**
* The origin this database item was retrieved from or undefined if unknown.
*/
readonly origin: DatabaseOrigin | undefined;
/** If the database is invalid, describes why. */
readonly error: Error | undefined;

View File

@@ -7,6 +7,7 @@ import { QueryRunner } from "../../query-server";
import * as cli from "../../codeql-cli/cli";
import { ProgressCallback, withProgress } from "../../common/vscode/progress";
import {
addDatabaseSourceToWorkspace,
getAutogenerateQlPacks,
isCodespacesTemplate,
setAutogenerateQlPacks,
@@ -34,6 +35,7 @@ import { DatabaseChangedEvent, DatabaseEventKind } from "./database-events";
import { DatabaseResolver } from "./database-resolver";
import { telemetryListener } from "../../common/vscode/telemetry";
import { LanguageContextStore } from "../../language-context-store";
import { DatabaseOrigin } from "./database-origin";
/**
* The name of the key in the workspaceState dictionary in which we
@@ -115,7 +117,7 @@ export class DatabaseManager extends DisposableObject {
this.languageContext.onLanguageContextChanged(async () => {
if (
this.currentDatabaseItem !== undefined &&
!this.languageContext.isSelectedLanguage(
!this.languageContext.shouldInclude(
tryGetQueryLanguage(this.currentDatabaseItem.language),
)
) {
@@ -131,14 +133,19 @@ export class DatabaseManager extends DisposableObject {
*/
public async openDatabase(
uri: vscode.Uri,
origin: DatabaseOrigin | undefined,
makeSelected = true,
displayName?: string,
{
isTutorialDatabase = false,
addSourceArchiveFolder = true,
addSourceArchiveFolder = addDatabaseSourceToWorkspace(),
}: OpenDatabaseOptions = {},
): Promise<DatabaseItem> {
const databaseItem = await this.createDatabaseItem(uri, displayName);
const databaseItem = await this.createDatabaseItem(
uri,
origin,
displayName,
);
return await this.addExistingDatabaseItem(
databaseItem,
@@ -158,7 +165,7 @@ export class DatabaseManager extends DisposableObject {
databaseItem: DatabaseItemImpl,
makeSelected: boolean,
isTutorialDatabase?: boolean,
addSourceArchiveFolder = true,
addSourceArchiveFolder = addDatabaseSourceToWorkspace(),
): Promise<DatabaseItem> {
const existingItem = this.findDatabaseItem(databaseItem.databaseUri);
if (existingItem !== undefined) {
@@ -189,6 +196,7 @@ export class DatabaseManager extends DisposableObject {
*/
private async createDatabaseItem(
uri: vscode.Uri,
origin: DatabaseOrigin | undefined,
displayName: string | undefined,
): Promise<DatabaseItemImpl> {
const contents = await DatabaseResolver.resolveDatabaseContents(uri);
@@ -197,6 +205,7 @@ export class DatabaseManager extends DisposableObject {
displayName,
dateAdded: Date.now(),
language: await this.getPrimaryLanguage(uri.fsPath),
origin,
};
const databaseItem = new DatabaseItemImpl(uri, contents, fullOptions);
@@ -212,6 +221,7 @@ export class DatabaseManager extends DisposableObject {
*/
public async createOrOpenDatabaseItem(
uri: vscode.Uri,
origin: DatabaseOrigin | undefined,
): Promise<DatabaseItem> {
const existingItem = this.findDatabaseItem(uri);
if (existingItem !== undefined) {
@@ -220,7 +230,7 @@ export class DatabaseManager extends DisposableObject {
}
// We don't add this to the list automatically, but the user can add it later.
return this.createDatabaseItem(uri, undefined);
return this.createDatabaseItem(uri, origin, undefined);
}
public async createSkeletonPacks(databaseItem: DatabaseItem) {
@@ -355,6 +365,7 @@ export class DatabaseManager extends DisposableObject {
let displayName: string | undefined = undefined;
let dateAdded = undefined;
let language = undefined;
let origin = undefined;
if (state.options) {
if (typeof state.options.displayName === "string") {
displayName = state.options.displayName;
@@ -363,6 +374,7 @@ export class DatabaseManager extends DisposableObject {
dateAdded = state.options.dateAdded;
}
language = state.options.language;
origin = state.options.origin;
}
const dbBaseUri = vscode.Uri.parse(state.uri, true);
@@ -375,6 +387,7 @@ export class DatabaseManager extends DisposableObject {
displayName,
dateAdded,
language,
origin,
};
const item = new DatabaseItemImpl(dbBaseUri, undefined, fullOptions);

View File

@@ -1,10 +1,14 @@
import { DatabaseOrigin } from "./database-origin";
export interface DatabaseOptions {
displayName?: string;
dateAdded?: number | undefined;
language?: string;
origin?: DatabaseOrigin;
}
export interface FullDatabaseOptions extends DatabaseOptions {
dateAdded: number | undefined;
language: string | undefined;
origin: DatabaseOrigin | undefined;
}

View File

@@ -0,0 +1,32 @@
interface DatabaseOriginFolder {
type: "folder";
}
interface DatabaseOriginArchive {
type: "archive";
path: string;
}
interface DatabaseOriginGitHub {
type: "github";
repository: string;
databaseId: number;
databaseCreatedAt: string;
commitOid: string | null;
}
interface DatabaseOriginInternet {
type: "url";
url: string;
}
interface DatabaseOriginDebugger {
type: "debugger";
}
export type DatabaseOrigin =
| DatabaseOriginFolder
| DatabaseOriginArchive
| DatabaseOriginGitHub
| DatabaseOriginInternet
| DatabaseOriginDebugger;

View File

@@ -105,7 +105,9 @@ class QLDebugAdapterTracker
body: CodeQLProtocol.EvaluationStartedEvent["body"],
): Promise<void> {
const dbUri = Uri.file(this.configuration.database);
const dbItem = await this.dbm.createOrOpenDatabaseItem(dbUri);
const dbItem = await this.dbm.createOrOpenDatabaseItem(dbUri, {
type: "debugger",
});
// When cancellation is requested from the query history view, we just stop the debug session.
const tokenSource = new CancellationTokenSource();

View File

@@ -1,4 +1,4 @@
import { mkdir, writeFile } from "fs-extra";
import { ensureDir, writeFile } from "fs-extra";
import { dump } from "js-yaml";
import { dirname, join } from "path";
import { Uri } from "vscode";
@@ -76,7 +76,7 @@ export class QlPackGenerator {
}
private async createWorkspaceFolder() {
await mkdir(this.folderUri.fsPath);
await ensureDir(this.folderUri.fsPath);
}
private async createQlPackYaml() {

View File

@@ -4,7 +4,6 @@ import { DisposableObject } from "../../common/disposable-object";
import { MethodModelingViewProvider } from "./method-modeling-view-provider";
import { Method } from "../method";
import { ModelingStore } from "../modeling-store";
import { ModelEditorViewTracker } from "../model-editor-view-tracker";
import { ModelConfigListener } from "../../config";
import { DatabaseItem } from "../../databases/local-databases";
import { ModelingEvents } from "../modeling-events";
@@ -16,7 +15,6 @@ export class MethodModelingPanel extends DisposableObject {
app: App,
modelingStore: ModelingStore,
modelingEvents: ModelingEvents,
editorViewTracker: ModelEditorViewTracker,
) {
super();
@@ -29,7 +27,6 @@ export class MethodModelingPanel extends DisposableObject {
app,
modelingStore,
modelingEvents,
editorViewTracker,
modelConfig,
);
this.push(

View File

@@ -10,7 +10,6 @@ import { Method } from "../method";
import { ModelingStore } from "../modeling-store";
import { AbstractWebviewViewProvider } from "../../common/vscode/abstract-webview-view-provider";
import { assertNever } from "../../common/helpers-pure";
import { ModelEditorViewTracker } from "../model-editor-view-tracker";
import { ModelConfigListener } from "../../config";
import { DatabaseItem } from "../../databases/local-databases";
import { ModelingEvents } from "../modeling-events";
@@ -33,7 +32,6 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
app: App,
private readonly modelingStore: ModelingStore,
private readonly modelingEvents: ModelingEvents,
private readonly editorViewTracker: ModelEditorViewTracker,
private readonly modelConfig: ModelConfigListener,
) {
super(app, "method-modeling");
@@ -158,10 +156,10 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
return;
}
const view = this.editorViewTracker.getView(
this.modelingEvents.fireRevealInModelEditorEvent(
this.databaseItem.databaseUri.toString(),
method,
);
await view?.revealMethod(method);
}
private registerToModelingEvents(): void {

View File

@@ -19,7 +19,6 @@ 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";
import { ModelEditorViewTracker } from "./model-editor-view-tracker";
import { ModelConfigListener } from "../config";
import { ModelingEvents } from "./modeling-events";
import { getModelsAsDataLanguage } from "./languages";
@@ -30,7 +29,6 @@ export class ModelEditorModule extends DisposableObject {
private readonly queryStorageDir: string;
private readonly modelingStore: ModelingStore;
private readonly modelingEvents: ModelingEvents;
private readonly editorViewTracker: ModelEditorViewTracker<ModelEditorView>;
private readonly methodsUsagePanel: MethodsUsagePanel;
private readonly methodModelingPanel: MethodModelingPanel;
private readonly modelConfig: ModelConfigListener;
@@ -46,17 +44,11 @@ export class ModelEditorModule extends DisposableObject {
this.queryStorageDir = join(baseQueryStorageDir, "model-editor-results");
this.modelingEvents = new ModelingEvents(app);
this.modelingStore = new ModelingStore(this.modelingEvents);
this.editorViewTracker = new ModelEditorViewTracker();
this.methodsUsagePanel = this.push(
new MethodsUsagePanel(this.modelingStore, this.modelingEvents, cliServer),
);
this.methodModelingPanel = this.push(
new MethodModelingPanel(
app,
this.modelingStore,
this.modelingEvents,
this.editorViewTracker,
),
new MethodModelingPanel(app, this.modelingStore, this.modelingEvents),
);
this.modelConfig = this.push(new ModelConfigListener());
@@ -144,12 +136,10 @@ export class ModelEditorModule extends DisposableObject {
const initialMode = definition.availableModes?.[0] ?? INITIAL_MODE;
const existingView = this.editorViewTracker.getView(
db.databaseUri.toString(),
);
if (existingView) {
await existingView.focusView();
if (this.modelingStore.isDbOpen(db.databaseUri.toString())) {
this.modelingEvents.fireFocusModelEditorEvent(
db.databaseUri.toString(),
);
return;
}
@@ -218,12 +208,10 @@ export class ModelEditorModule extends DisposableObject {
// Check again just before opening the editor to ensure no model editor has been opened between
// our first check and now.
const existingView = this.editorViewTracker.getView(
db.databaseUri.toString(),
);
if (existingView) {
await existingView.focusView();
if (this.modelingStore.isDbOpen(db.databaseUri.toString())) {
this.modelingEvents.fireFocusModelEditorEvent(
db.databaseUri.toString(),
);
return;
}
@@ -231,7 +219,6 @@ export class ModelEditorModule extends DisposableObject {
this.app,
this.modelingStore,
this.modelingEvents,
this.editorViewTracker,
this.modelConfig,
this.databaseManager,
this.cliServer,

View File

@@ -1,33 +0,0 @@
import { Method } from "./method";
interface ModelEditorViewInterface {
databaseUri: string;
revealMethod(method: Method): Promise<void>;
}
export class ModelEditorViewTracker<
T extends ModelEditorViewInterface = ModelEditorViewInterface,
> {
private readonly views = new Map<string, T>();
constructor() {}
public registerView(view: T): void {
const databaseUri = view.databaseUri;
if (this.views.has(databaseUri)) {
throw new Error(`View for database ${databaseUri} already registered`);
}
this.views.set(databaseUri, view);
}
public unregisterView(view: T): void {
this.views.delete(view.databaseUri);
}
public getView(databaseUri: string): T | undefined {
return this.views.get(databaseUri);
}
}

View File

@@ -44,7 +44,6 @@ import {
import { AutoModeler } from "./auto-modeler";
import { telemetryListener } from "../common/vscode/telemetry";
import { ModelingStore } from "./modeling-store";
import { ModelEditorViewTracker } from "./model-editor-view-tracker";
import { ModelingEvents } from "./modeling-events";
import { getModelsAsDataLanguage, ModelsAsDataLanguage } from "./languages";
import { runGenerateQueries } from "./generate";
@@ -60,7 +59,6 @@ export class ModelEditorView extends AbstractWebview<
protected readonly app: App,
private readonly modelingStore: ModelingStore,
private readonly modelingEvents: ModelingEvents,
private readonly viewTracker: ModelEditorViewTracker<ModelEditorView>,
private readonly modelConfig: ModelConfigListener,
private readonly databaseManager: DatabaseManager,
private readonly cliServer: CodeQLCliServer,
@@ -79,8 +77,6 @@ export class ModelEditorView extends AbstractWebview<
this.registerToModelingEvents();
this.registerToModelConfigEvents();
this.viewTracker.registerView(this);
this.autoModeler = new AutoModeler(
app,
cliServer,
@@ -166,7 +162,7 @@ export class ModelEditorView extends AbstractWebview<
}
protected onPanelDispose(): void {
this.viewTracker.unregisterView(this);
// Nothing to do
}
protected async onMessage(msg: FromModelEditorMessage): Promise<void> {
@@ -573,12 +569,9 @@ export class ModelEditorView extends AbstractWebview<
return;
}
let existingView = this.viewTracker.getView(
addedDatabase.databaseUri.toString(),
);
if (existingView) {
await existingView.focusView();
const addedDbUri = addedDatabase.databaseUri.toString();
if (this.modelingStore.isDbOpen(addedDbUri)) {
this.modelingEvents.fireFocusModelEditorEvent(addedDbUri);
return;
}
@@ -596,12 +589,8 @@ export class ModelEditorView extends AbstractWebview<
// Check again just before opening the editor to ensure no model editor has been opened between
// our first check and now.
existingView = this.viewTracker.getView(
addedDatabase.databaseUri.toString(),
);
if (existingView) {
await existingView.focusView();
if (this.modelingStore.isDbOpen(addedDbUri)) {
this.modelingEvents.fireFocusModelEditorEvent(addedDbUri);
return;
}
@@ -609,7 +598,6 @@ export class ModelEditorView extends AbstractWebview<
this.app,
this.modelingStore,
this.modelingEvents,
this.viewTracker,
this.modelConfig,
this.databaseManager,
this.cliServer,
@@ -742,6 +730,22 @@ export class ModelEditorView extends AbstractWebview<
}
}),
);
this.push(
this.modelingEvents.onRevealInModelEditor(async (event) => {
if (event.dbUri === this.databaseItem.databaseUri.toString()) {
await this.revealMethod(event.method);
}
}),
);
this.push(
this.modelingEvents.onFocusModelEditor(async (event) => {
if (event.dbUri === this.databaseItem.databaseUri.toString()) {
await this.focusView();
}
}),
);
}
private registerToModelConfigEvents() {

View File

@@ -126,3 +126,38 @@ export function isModelAccepted(
modeledMethod.provenance !== "ai-generated"
);
}
/**
* Calculates the new provenance for a modeled method based on the current provenance.
* @param modeledMethod The modeled method if there is one.
* @returns The new provenance.
*/
export function calculateNewProvenance(
modeledMethod: ModeledMethod | undefined,
) {
if (!modeledMethod || !modeledMethodSupportsProvenance(modeledMethod)) {
// If nothing has been modeled or the modeled method does not support
// provenance, we assume that the user has entered it manually.
return "manual";
}
switch (modeledMethod.provenance) {
case "df-generated":
// If the method has been generated and there has been a change, we assume
// that the user has manually edited it.
return "df-manual";
case "df-manual":
// If the method has had manual edits, we want the provenance to stay the same.
return "df-manual";
case "ai-generated":
// If the method has been generated and there has been a change, we assume
// that the user has manually edited it.
return "ai-manual";
case "ai-manual":
// If the method has had manual edits, we want the provenance to stay the same.
return "ai-manual";
default:
// The method has been modeled manually.
return "manual";
}
}

View File

@@ -48,6 +48,15 @@ interface InProgressMethodsChangedEvent {
readonly methods: ReadonlySet<string>;
}
interface RevealInModelEditorEvent {
dbUri: string;
method: Method;
}
interface FocusModelEditorEvent {
dbUri: string;
}
export class ModelingEvents extends DisposableObject {
public readonly onActiveDbChanged: AppEvent<void>;
public readonly onDbOpened: AppEvent<DatabaseItem>;
@@ -59,6 +68,8 @@ export class ModelingEvents extends DisposableObject {
public readonly onModifiedMethodsChanged: AppEvent<ModifiedMethodsChangedEvent>;
public readonly onSelectedMethodChanged: AppEvent<SelectedMethodChangedEvent>;
public readonly onInProgressMethodsChanged: AppEvent<InProgressMethodsChangedEvent>;
public readonly onRevealInModelEditor: AppEvent<RevealInModelEditorEvent>;
public readonly onFocusModelEditor: AppEvent<FocusModelEditorEvent>;
private readonly onActiveDbChangedEventEmitter: AppEventEmitter<void>;
private readonly onDbOpenedEventEmitter: AppEventEmitter<DatabaseItem>;
@@ -70,6 +81,8 @@ export class ModelingEvents extends DisposableObject {
private readonly onModifiedMethodsChangedEventEmitter: AppEventEmitter<ModifiedMethodsChangedEvent>;
private readonly onSelectedMethodChangedEventEmitter: AppEventEmitter<SelectedMethodChangedEvent>;
private readonly onInProgressMethodsChangedEventEmitter: AppEventEmitter<InProgressMethodsChangedEvent>;
private readonly onRevealInModelEditorEventEmitter: AppEventEmitter<RevealInModelEditorEvent>;
private readonly onFocusModelEditorEventEmitter: AppEventEmitter<FocusModelEditorEvent>;
constructor(app: App) {
super();
@@ -126,6 +139,16 @@ export class ModelingEvents extends DisposableObject {
);
this.onInProgressMethodsChanged =
this.onInProgressMethodsChangedEventEmitter.event;
this.onRevealInModelEditorEventEmitter = this.push(
app.createEventEmitter<RevealInModelEditorEvent>(),
);
this.onRevealInModelEditor = this.onRevealInModelEditorEventEmitter.event;
this.onFocusModelEditorEventEmitter = this.push(
app.createEventEmitter<FocusModelEditorEvent>(),
);
this.onFocusModelEditor = this.onFocusModelEditorEventEmitter.event;
}
public fireActiveDbChangedEvent() {
@@ -220,4 +243,17 @@ export class ModelingEvents extends DisposableObject {
methods,
});
}
public fireRevealInModelEditorEvent(dbUri: string, method: Method) {
this.onRevealInModelEditorEventEmitter.fire({
dbUri,
method,
});
}
public fireFocusModelEditorEvent(dbUri: string) {
this.onFocusModelEditorEventEmitter.fire({
dbUri,
});
}
}

View File

@@ -112,6 +112,10 @@ export class ModelingStore extends DisposableObject {
return this.state.size > 0;
}
public isDbOpen(dbUri: string): boolean {
return this.state.has(dbUri);
}
/**
* Returns the method for the given database item and method signature.
* Returns undefined if no method exists with that signature.

View File

@@ -1,7 +1,6 @@
import { CodeQLCliServer } from "../codeql-cli/cli";
import { extLogger } from "../common/logging/vscode";
import { App } from "../common/app";
import { isCanary, showQueriesPanel } from "../config";
import { DisposableObject } from "../common/disposable-object";
import { QueriesPanel } from "./queries-panel";
import { QueryDiscovery } from "./query-discovery";
@@ -41,11 +40,6 @@ export class QueriesModule extends DisposableObject {
langauageContext: LanguageContextStore,
cliServer: CodeQLCliServer,
): void {
// Currently, we only want to expose the new panel when we are in canary mode
// and the user has enabled the "Show queries panel" flag.
if (!isCanary() || !showQueriesPanel()) {
return;
}
void extLogger.log("Initializing queries panel.");
const queryPackDiscovery = new QueryPackDiscovery(cliServer);

View File

@@ -103,6 +103,7 @@ export class TestRunner extends DisposableObject {
try {
const reopenedDatabase = await this.databaseManager.openDatabase(
uri,
closedDatabase.origin,
false,
);
await this.databaseManager.renameDatabaseItem(

View File

@@ -2,6 +2,7 @@ import * as React from "react";
import { ChangeEvent, useCallback, useMemo } from "react";
import {
ModeledMethod,
calculateNewProvenance,
isModelAccepted,
modeledMethodSupportsInput,
} from "../../model-editor/modeled-method";
@@ -53,6 +54,7 @@ export const ModelInputDropdown = ({
onChange({
...modeledMethod,
provenance: calculateNewProvenance(modeledMethod),
input: target.value,
});
},

View File

@@ -5,6 +5,7 @@ import {
ModeledMethodKind,
modeledMethodSupportsKind,
isModelAccepted,
calculateNewProvenance,
} from "../../model-editor/modeled-method";
import { getModelsAsDataLanguage } from "../../model-editor/languages";
import { QueryLanguage } from "../../common/query-language";
@@ -52,6 +53,7 @@ export const ModelKindDropdown = ({
onChange({
...modeledMethod,
provenance: calculateNewProvenance(modeledMethod),
kind,
});
},

View File

@@ -2,6 +2,7 @@ import * as React from "react";
import { ChangeEvent, useCallback, useMemo } from "react";
import {
ModeledMethod,
calculateNewProvenance,
isModelAccepted,
modeledMethodSupportsOutput,
} from "../../model-editor/modeled-method";
@@ -54,6 +55,7 @@ export const ModelOutputDropdown = ({
onChange({
...modeledMethod,
provenance: calculateNewProvenance(modeledMethod),
output: target.value,
});
},

View File

@@ -1,11 +1,10 @@
import * as React from "react";
import { ChangeEvent, useCallback } from "react";
import {
calculateNewProvenance,
isModelAccepted,
ModeledMethod,
modeledMethodSupportsProvenance,
ModeledMethodType,
Provenance,
} from "../../model-editor/modeled-method";
import { Method } from "../../model-editor/method";
import { createEmptyModeledMethod } from "../../model-editor/modeled-method-empty";
@@ -43,15 +42,6 @@ export const ModelTypeDropdown = ({
(e: ChangeEvent<HTMLSelectElement>) => {
const modelsAsDataLanguage = getModelsAsDataLanguage(language);
let newProvenance: Provenance = "manual";
if (modeledMethod && modeledMethodSupportsProvenance(modeledMethod)) {
if (modeledMethod.provenance === "df-generated") {
newProvenance = "df-manual";
} else if (modeledMethod.provenance === "ai-generated") {
newProvenance = "ai-manual";
}
}
const emptyModeledMethod = createEmptyModeledMethod(
e.target.value as ModeledMethodType,
method,
@@ -67,7 +57,7 @@ export const ModelTypeDropdown = ({
updatedModeledMethod.output = "ReturnValue";
}
if ("provenance" in updatedModeledMethod) {
updatedModeledMethod.provenance = newProvenance;
updatedModeledMethod.provenance = calculateNewProvenance(modeledMethod);
}
if ("kind" in updatedModeledMethod) {
updatedModeledMethod.kind = "value";

View File

@@ -27,7 +27,7 @@ describe(MethodRow.name, () => {
input: "Argument[0]",
output: "ReturnValue",
kind: "taint",
provenance: "df-generated",
provenance: "manual",
};
const onChange = jest.fn();
@@ -111,6 +111,32 @@ describe(MethodRow.name, () => {
]);
});
it("changes the provenance when the kind is changed", async () => {
const modeledMethodWithGeneratedProvenance: ModeledMethod = {
...modeledMethod,
provenance: "df-generated",
};
render({ modeledMethods: [modeledMethodWithGeneratedProvenance] });
onChange.mockReset();
expect(screen.getByRole("combobox", { name: "Kind" })).toHaveValue("taint");
await userEvent.selectOptions(
screen.getByRole("combobox", { name: "Kind" }),
"value",
);
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange).toHaveBeenCalledWith(method.signature, [
{
...modeledMethod,
kind: "value",
provenance: "df-manual",
},
]);
});
it("has the correct input options", () => {
render();

View File

@@ -1,5 +1,5 @@
[
"v2.15.1",
"v2.15.2",
"v2.14.6",
"v2.13.5",
"v2.12.7",

View File

@@ -1,19 +0,0 @@
import { mockedObject } from "../../vscode-tests/utils/mocking.helpers";
import { ModelEditorViewTracker } from "../../../src/model-editor/model-editor-view-tracker";
import { ModelEditorView } from "../../../src/model-editor/model-editor-view";
export function createMockModelEditorViewTracker({
registerView = jest.fn(),
unregisterView = jest.fn(),
getView = jest.fn(),
}: {
registerView?: ModelEditorViewTracker["registerView"];
unregisterView?: ModelEditorViewTracker["unregisterView"];
getView?: ModelEditorViewTracker["getView"];
} = {}): ModelEditorViewTracker<ModelEditorView> {
return mockedObject<ModelEditorViewTracker<ModelEditorView>>({
registerView,
unregisterView,
getView,
});
}

View File

@@ -10,6 +10,8 @@ export function createMockModelingEvents({
onModeledMethodsChanged = jest.fn(),
onModifiedMethodsChanged = jest.fn(),
onInProgressMethodsChanged = jest.fn(),
onRevealInModelEditor = jest.fn(),
onFocusModelEditor = jest.fn(),
}: {
onActiveDbChanged?: ModelingEvents["onActiveDbChanged"];
onDbClosed?: ModelingEvents["onDbClosed"];
@@ -19,6 +21,8 @@ export function createMockModelingEvents({
onModeledMethodsChanged?: ModelingEvents["onModeledMethodsChanged"];
onModifiedMethodsChanged?: ModelingEvents["onModifiedMethodsChanged"];
onInProgressMethodsChanged?: ModelingEvents["onInProgressMethodsChanged"];
onRevealInModelEditor?: ModelingEvents["onRevealInModelEditor"];
onFocusModelEditor?: ModelingEvents["onFocusModelEditor"];
} = {}): ModelingEvents {
return mockedObject<ModelingEvents>({
onActiveDbChanged,
@@ -29,5 +33,7 @@ export function createMockModelingEvents({
onModeledMethodsChanged,
onModifiedMethodsChanged,
onInProgressMethodsChanged,
onRevealInModelEditor,
onFocusModelEditor,
});
}

View File

@@ -11,6 +11,9 @@ export function mockDbOptions(): FullDatabaseOptions {
return {
dateAdded: 123,
language: "",
origin: {
type: "folder",
},
};
}

View File

@@ -7,6 +7,7 @@ import {
SelectedDbItem,
DB_CONFIG_VERSION,
} from "../../src/databases/config/db-config";
import { DatabaseOrigin } from "../../src/databases/local-databases/database-origin";
export function createDbConfig({
remoteLists = [],
@@ -45,16 +46,21 @@ export function createLocalDbConfigItem({
dateAdded = faker.date.past().getTime(),
language = `language${faker.number.int()}`,
storagePath = `storagePath${faker.number.int()}`,
origin = {
type: "folder",
},
}: {
name?: string;
dateAdded?: number;
language?: string;
storagePath?: string;
origin?: DatabaseOrigin;
} = {}): LocalDatabase {
return {
name,
dateAdded,
language,
storagePath,
origin,
};
}

View File

@@ -12,6 +12,7 @@ import {
RootLocalDbItem,
RootRemoteDbItem,
} from "../../src/databases/db-item";
import { DatabaseOrigin } from "../../src/databases/local-databases/database-origin";
// Root Remote Db Items
export function createRootRemoteDbItem({
@@ -124,12 +125,16 @@ export function createLocalDatabaseDbItem({
language = `language${faker.number.int()}`,
storagePath = `storagePath${faker.number.int()}`,
selected = false,
origin = {
type: "folder",
},
}: {
databaseName?: string;
dateAdded?: number;
language?: string;
storagePath?: string;
selected?: boolean;
origin?: DatabaseOrigin;
} = {}): LocalDatabaseDbItem {
return {
kind: DbItemKind.LocalDatabase,
@@ -138,6 +143,7 @@ export function createLocalDatabaseDbItem({
dateAdded,
language,
storagePath,
origin,
};
}

View File

@@ -57,6 +57,9 @@ describe("db item selection", () => {
dateAdded: 1234,
language: "javascript",
storagePath: "/foo/bar",
origin: {
type: "folder",
},
selected: true,
});
});

View File

@@ -337,12 +337,18 @@ describe("db tree creator", () => {
dateAdded: 1668428293677,
language: QueryLanguage.Cpp,
storagePath: "/path/to/db1/",
origin: {
type: "folder",
},
},
{
name: "db2",
dateAdded: 1668428472731,
language: "cpp",
storagePath: "/path/to/db2/",
origin: {
type: "folder",
},
},
],
},
@@ -354,6 +360,9 @@ describe("db tree creator", () => {
dateAdded: 1668428472731,
language: "ruby",
storagePath: "/path/to/db3/",
origin: {
type: "folder",
},
},
],
},
@@ -380,6 +389,7 @@ describe("db tree creator", () => {
databaseName: db.name,
dateAdded: db.dateAdded,
language: db.language,
origin: db.origin,
storagePath: db.storagePath,
parentListName: dbConfig.databases.local.lists[0].name,
})),
@@ -395,6 +405,7 @@ describe("db tree creator", () => {
databaseName: db.name,
dateAdded: db.dateAdded,
language: db.language,
origin: db.origin,
storagePath: db.storagePath,
parentListName: dbConfig.databases.local.lists[1].name,
})),
@@ -409,12 +420,18 @@ describe("db tree creator", () => {
dateAdded: 1668428293677,
language: "csharp",
storagePath: "/path/to/db1/",
origin: {
type: "folder",
},
},
{
name: "db2",
dateAdded: 1668428472731,
language: "go",
storagePath: "/path/to/db2/",
origin: {
type: "folder",
},
},
],
});
@@ -434,6 +451,7 @@ describe("db tree creator", () => {
databaseName: dbConfig.databases.local.databases[0].name,
dateAdded: dbConfig.databases.local.databases[0].dateAdded,
language: dbConfig.databases.local.databases[0].language,
origin: dbConfig.databases.local.databases[0].origin,
storagePath: dbConfig.databases.local.databases[0].storagePath,
});
expect(localDatabaseNodes[1]).toEqual({
@@ -442,6 +460,7 @@ describe("db tree creator", () => {
databaseName: dbConfig.databases.local.databases[1].name,
dateAdded: dbConfig.databases.local.databases[1].dateAdded,
language: dbConfig.databases.local.databases[1].language,
origin: dbConfig.databases.local.databases[1].origin,
storagePath: dbConfig.databases.local.databases[1].storagePath,
});
});

View File

@@ -186,12 +186,18 @@ describe("db panel rendering nodes", () => {
dateAdded: 1668428293677,
language: QueryLanguage.Cpp,
storagePath: "/path/to/db1/",
origin: {
type: "folder",
},
},
{
name: "db2",
dateAdded: 1668428472731,
language: QueryLanguage.Cpp,
storagePath: "/path/to/db2/",
origin: {
type: "folder",
},
},
],
},
@@ -203,6 +209,9 @@ describe("db panel rendering nodes", () => {
dateAdded: 1668428472731,
language: "ruby",
storagePath: "/path/to/db3/",
origin: {
type: "folder",
},
},
],
},
@@ -238,6 +247,9 @@ describe("db panel rendering nodes", () => {
language: QueryLanguage.Cpp,
storagePath: "/path/to/db1/",
selected: false,
origin: {
type: "folder",
},
},
{
kind: DbItemKind.LocalDatabase,
@@ -246,6 +258,9 @@ describe("db panel rendering nodes", () => {
language: QueryLanguage.Cpp,
storagePath: "/path/to/db2/",
selected: false,
origin: {
type: "folder",
},
},
]);
checkLocalListItem(localListItems[1], "my-list-2", [
@@ -256,6 +271,9 @@ describe("db panel rendering nodes", () => {
language: "ruby",
storagePath: "/path/to/db3/",
selected: false,
origin: {
type: "folder",
},
},
]);
});
@@ -268,12 +286,18 @@ describe("db panel rendering nodes", () => {
dateAdded: 1668428293677,
language: "csharp",
storagePath: "/path/to/db1/",
origin: {
type: "folder",
},
},
{
name: "db2",
dateAdded: 1668428472731,
language: "go",
storagePath: "/path/to/db2/",
origin: {
type: "folder",
},
},
],
});
@@ -306,6 +330,9 @@ describe("db panel rendering nodes", () => {
language: "csharp",
storagePath: "/path/to/db1/",
selected: false,
origin: {
type: "folder",
},
});
checkLocalDatabaseItem(localDatabaseItems[1], {
kind: DbItemKind.LocalDatabase,
@@ -314,6 +341,9 @@ describe("db panel rendering nodes", () => {
language: "go",
storagePath: "/path/to/db2/",
selected: false,
origin: {
type: "folder",
},
});
});
});

View File

@@ -597,6 +597,9 @@ describe("local databases", () => {
const options: FullDatabaseOptions = {
dateAdded: 123,
language,
origin: {
type: "folder",
},
};
mockDbItem = createMockDB(dir, options);
@@ -728,19 +731,41 @@ describe("local databases", () => {
});
it("should resolve the database contents", async () => {
await databaseManager.openDatabase(mockDbItem.databaseUri);
await databaseManager.openDatabase(
mockDbItem.databaseUri,
mockDbItem.origin,
);
expect(resolveDatabaseContentsSpy).toBeCalledTimes(2);
});
it("should set the database as the currently selected one", async () => {
await databaseManager.openDatabase(mockDbItem.databaseUri);
await databaseManager.openDatabase(
mockDbItem.databaseUri,
mockDbItem.origin,
);
expect(setCurrentDatabaseItemSpy).toBeCalledTimes(1);
});
it("should add database source archive folder", async () => {
await databaseManager.openDatabase(mockDbItem.databaseUri);
it("should not add database source archive folder when `codeQL.addingDatabases.addDatabaseSourceToWorkspace` is `false`", async () => {
jest.spyOn(config, "addDatabaseSourceToWorkspace").mockReturnValue(false);
await databaseManager.openDatabase(
mockDbItem.databaseUri,
mockDbItem.origin,
);
expect(addDatabaseSourceArchiveFolderSpy).toBeCalledTimes(0);
});
it("should add database source archive folder when `codeQL.addingDatabases.addDatabaseSourceToWorkspace` is `true`", async () => {
jest.spyOn(config, "addDatabaseSourceToWorkspace").mockReturnValue(true);
await databaseManager.openDatabase(
mockDbItem.databaseUri,
mockDbItem.origin,
);
expect(addDatabaseSourceArchiveFolderSpy).toBeCalledTimes(1);
});
@@ -756,6 +781,7 @@ describe("local databases", () => {
await databaseManager.openDatabase(
mockDbItem.databaseUri,
mockDbItem.origin,
makeSelected,
nameOverride,
{ isTutorialDatabase },
@@ -769,7 +795,10 @@ describe("local databases", () => {
it("should create a skeleton QL pack", async () => {
jest.spyOn(config, "isCodespacesTemplate").mockReturnValue(true);
await databaseManager.openDatabase(mockDbItem.databaseUri);
await databaseManager.openDatabase(
mockDbItem.databaseUri,
mockDbItem.origin,
);
expect(createSkeletonPacksSpy).toBeCalledTimes(1);
});
@@ -780,7 +809,10 @@ describe("local databases", () => {
it("should not create a skeleton QL pack", async () => {
jest.spyOn(config, "isCodespacesTemplate").mockReturnValue(false);
await databaseManager.openDatabase(mockDbItem.databaseUri);
await databaseManager.openDatabase(
mockDbItem.databaseUri,
mockDbItem.origin,
);
expect(createSkeletonPacksSpy).toBeCalledTimes(0);
});
});

View File

@@ -8,7 +8,11 @@ import {
findDirWithFile,
} from "../../../../src/databases/database-fetcher";
import * as Octokit from "@octokit/rest";
import { mockedQuickPickItem } from "../../utils/mocking.helpers";
import {
mockedObject,
mockedOctokitFunction,
mockedQuickPickItem,
} from "../../utils/mocking.helpers";
// These tests make API calls and may need extra time to complete.
jest.setTimeout(10000);
@@ -18,10 +22,17 @@ describe("database-fetcher", () => {
let quickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
const progressSpy = jest.fn();
const mockRequest = jest.fn();
const octokit: Octokit.Octokit = {
request: mockRequest,
} as unknown as Octokit.Octokit;
const mockListCodeqlDatabases = mockedOctokitFunction<
"codeScanning",
"listCodeqlDatabases"
>();
const octokit = mockedObject<Octokit.Octokit>({
rest: {
codeScanning: {
listCodeqlDatabases: mockListCodeqlDatabases,
},
},
});
// We can't make the real octokit request (since we need credentials), so we mock the response.
const successfullMockApiResponse = {
@@ -72,7 +83,7 @@ describe("database-fetcher", () => {
});
it("should convert a GitHub nwo to a database url", async () => {
mockRequest.mockResolvedValue(successfullMockApiResponse);
mockListCodeqlDatabases.mockResolvedValue(successfullMockApiResponse);
quickPickSpy.mockResolvedValue(
mockedQuickPickItem({
label: "JavaScript",
@@ -93,7 +104,7 @@ describe("database-fetcher", () => {
const { databaseUrl, name, owner } = result;
expect(databaseUrl).toBe(
"https://api.github.com/repos/github/codeql/code-scanning/codeql/databases/javascript",
"https://api.github.com/repositories/143040428/code-scanning/codeql/databases/javascript",
);
expect(name).toBe("codeql");
expect(owner).toBe("github");
@@ -128,7 +139,7 @@ describe("database-fetcher", () => {
},
status: 404,
};
mockRequest.mockResolvedValue(mockApiResponse);
mockListCodeqlDatabases.mockResolvedValue(mockApiResponse);
const githubRepo = "foo/bar-not-real";
await expect(
convertGithubNwoToDatabaseUrl(githubRepo, octokit, progressSpy),
@@ -142,7 +153,7 @@ describe("database-fetcher", () => {
data: [],
};
mockRequest.mockResolvedValue(mockApiResponse);
mockListCodeqlDatabases.mockResolvedValue(mockApiResponse);
const githubRepo = "foo/bar-with-no-dbs";
await expect(
convertGithubNwoToDatabaseUrl(githubRepo, octokit, progressSpy),
@@ -153,7 +164,7 @@ describe("database-fetcher", () => {
describe("when language is already provided", () => {
describe("when language is valid", () => {
it("should not prompt the user", async () => {
mockRequest.mockResolvedValue(successfullMockApiResponse);
mockListCodeqlDatabases.mockResolvedValue(successfullMockApiResponse);
const githubRepo = "github/codeql";
await convertGithubNwoToDatabaseUrl(
githubRepo,
@@ -167,7 +178,7 @@ describe("database-fetcher", () => {
describe("when language is invalid", () => {
it("should prompt for language", async () => {
mockRequest.mockResolvedValue(successfullMockApiResponse);
mockListCodeqlDatabases.mockResolvedValue(successfullMockApiResponse);
const githubRepo = "github/codeql";
await convertGithubNwoToDatabaseUrl(
githubRepo,
@@ -182,7 +193,7 @@ describe("database-fetcher", () => {
describe("when language is not provided", () => {
it("should prompt for language", async () => {
mockRequest.mockResolvedValue(successfullMockApiResponse);
mockListCodeqlDatabases.mockResolvedValue(successfullMockApiResponse);
const githubRepo = "github/codeql";
await convertGithubNwoToDatabaseUrl(githubRepo, octokit, progressSpy);
expect(quickPickSpy).toHaveBeenCalled();

View File

@@ -9,7 +9,6 @@ import { mockEmptyDatabaseManager } from "../query-testing/test-runner-helpers";
import { QueryRunner } from "../../../../src/query-server";
import { ExtensionPack } from "../../../../src/model-editor/shared/extension-pack";
import { createMockModelingStore } from "../../../__mocks__/model-editor/modelingStoreMock";
import { createMockModelEditorViewTracker } from "../../../__mocks__/model-editor/modelEditorViewTrackerMock";
import { ModelConfigListener } from "../../../../src/config";
import { createMockModelingEvents } from "../../../__mocks__/model-editor/modelingEventsMock";
import { QueryLanguage } from "../../../../src/common/query-language";
@@ -18,7 +17,6 @@ describe("ModelEditorView", () => {
const app = createMockApp({});
const modelingStore = createMockModelingStore();
const modelingEvents = createMockModelingEvents();
const viewTracker = createMockModelEditorViewTracker();
const modelConfig = mockedObject<ModelConfigListener>({
onDidChangeConfiguration: jest.fn(),
});
@@ -48,7 +46,6 @@ describe("ModelEditorView", () => {
app,
modelingStore,
modelingEvents,
viewTracker,
modelConfig,
databaseManager,
cliServer,

View File

@@ -39,12 +39,18 @@ describe("test-runner", () => {
const preTestDatabaseItem = new DatabaseItemImpl(
Uri.file("/path/to/test/dir/dir.testproj"),
undefined,
mockedObject<FullDatabaseOptions>({ displayName: "custom display name" }),
mockedObject<FullDatabaseOptions>({
displayName: "custom display name",
origin: { type: "folder" },
}),
);
const postTestDatabaseItem = new DatabaseItemImpl(
Uri.file("/path/to/test/dir/dir.testproj"),
undefined,
mockedObject<FullDatabaseOptions>({ displayName: "default name" }),
mockedObject<FullDatabaseOptions>({
displayName: "default name",
origin: { type: "folder" },
}),
);
beforeEach(() => {
@@ -160,6 +166,7 @@ describe("test-runner", () => {
expect(openDatabaseSpy).toBeCalledTimes(1);
expect(openDatabaseSpy).toBeCalledWith(
preTestDatabaseItem.databaseUri,
preTestDatabaseItem.origin,
false,
);

View File

@@ -1,5 +1,6 @@
import type { QuickPickItem, window, Uri } from "vscode";
import { DatabaseItem } from "../../../src/databases/local-databases";
import * as Octokit from "@octokit/rest";
export type DeepPartial<T> = T extends object
? {
@@ -57,6 +58,14 @@ export function mockedObject<T extends object>(
});
}
export function mockedOctokitFunction<
Namespace extends keyof Octokit.Octokit["rest"],
Name extends keyof Octokit.Octokit["rest"][Namespace],
>(): Octokit.Octokit["rest"][Namespace][Name] & jest.Mock {
const fn = jest.fn();
return fn as unknown as Octokit.Octokit["rest"][Namespace][Name] & jest.Mock;
}
export function mockDatabaseItem(
props: DeepPartial<DatabaseItem> = {},
): DatabaseItem {