Add a language label next to databases in the UI
This change will only work on databases created by cli >= 2.4.1. In that version, a new `primaryLanguage` field in the `codeql-database.yml` file. We use this property as the language. This change also includes a refactoring of the logic around extracting database information heuristically based on file location. As much as possible, it is extracted to the `helpers` module. Also, the initial quick query text is generated based on the language (if known) otherwise it falls back to the old style of generation.
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
- Ensure databases are unlocked when removing them from the workspace. This will ensure that after a database is removed from VS Code, queries can be run on it from the command line without restarting VS Code. Requires CodeQL CLI 2.4.1 or later. [#681](https://github.com/github/vscode-codeql/pull/681)
|
- Ensure databases are unlocked when removing them from the workspace. This will ensure that after a database is removed from VS Code, queries can be run on it from the command line without restarting VS Code. Requires CodeQL CLI 2.4.1 or later. [#681](https://github.com/github/vscode-codeql/pull/681)
|
||||||
- Fix bug when removing databases where sometimes the source folder would not be removed from the workspace or the database files would not be removed from the workspace storage location. [#692](https://github.com/github/vscode-codeql/pull/692)
|
- Fix bug when removing databases where sometimes the source folder would not be removed from the workspace or the database files would not be removed from the workspace storage location. [#692](https://github.com/github/vscode-codeql/pull/692)
|
||||||
- Query results with no string representation will now be displayed with placeholder text in query results. Previously, they were omitted. [#694](https://github.com/github/vscode-codeql/pull/694)
|
- Query results with no string representation will now be displayed with placeholder text in query results. Previously, they were omitted. [#694](https://github.com/github/vscode-codeql/pull/694)
|
||||||
|
- Add a label for the language of a database in the databases view. This will only take effect for new databases created with the CodeQL CLI v2.4.1 or later. [#697](https://github.com/github/vscode-codeql/pull/697)
|
||||||
|
|
||||||
## 1.3.7 - 24 November 2020
|
## 1.3.7 - 24 November 2020
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export interface DbInfo {
|
|||||||
sourceArchiveRoot: string;
|
sourceArchiveRoot: string;
|
||||||
datasetFolder: string;
|
datasetFolder: string;
|
||||||
logsFolder: string;
|
logsFolder: string;
|
||||||
|
primaryLanguage: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ export async function qlpackOfDatabase(cli: CodeQLCliServer, db: DatabaseItem):
|
|||||||
if (db.contents === undefined)
|
if (db.contents === undefined)
|
||||||
return undefined;
|
return undefined;
|
||||||
const datasetPath = db.contents.datasetUri.fsPath;
|
const datasetPath = db.contents.datasetUri.fsPath;
|
||||||
const { qlpack } = await helpers.resolveDatasetFolder(cli, datasetPath);
|
return await helpers.getQlPackForDbscheme(cli, datasetPath);
|
||||||
return qlpack;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,15 +18,15 @@ import {
|
|||||||
DatabaseItem,
|
DatabaseItem,
|
||||||
DatabaseManager,
|
DatabaseManager,
|
||||||
getUpgradesDirectories,
|
getUpgradesDirectories,
|
||||||
isLikelyDatabaseRoot,
|
|
||||||
isLikelyDbLanguageFolder,
|
|
||||||
} from './databases';
|
} from './databases';
|
||||||
import {
|
import {
|
||||||
commandRunner,
|
commandRunner,
|
||||||
commandRunnerWithProgress,
|
commandRunnerWithProgress,
|
||||||
getOnDiskWorkspaceFolders,
|
getOnDiskWorkspaceFolders,
|
||||||
ProgressCallback,
|
ProgressCallback,
|
||||||
showAndLogErrorMessage
|
showAndLogErrorMessage,
|
||||||
|
isLikelyDatabaseRoot,
|
||||||
|
isLikelyDbLanguageFolder
|
||||||
} from './helpers';
|
} from './helpers';
|
||||||
import { logger } from './logging';
|
import { logger } from './logging';
|
||||||
import { clearCacheInDatabase } from './run-queries';
|
import { clearCacheInDatabase } from './run-queries';
|
||||||
@@ -143,6 +143,7 @@ class DatabaseTreeDataProvider extends DisposableObject
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
item.tooltip = element.databaseUri.fsPath;
|
item.tooltip = element.databaseUri.fsPath;
|
||||||
|
item.description = element.language;
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,15 @@ import * as path from 'path';
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as cli from './cli';
|
import * as cli from './cli';
|
||||||
import { ExtensionContext } from 'vscode';
|
import { ExtensionContext } from 'vscode';
|
||||||
import { showAndLogErrorMessage, showAndLogWarningMessage, showAndLogInformationMessage, ProgressCallback, withProgress } from './helpers';
|
import {
|
||||||
|
showAndLogErrorMessage,
|
||||||
|
showAndLogWarningMessage,
|
||||||
|
showAndLogInformationMessage,
|
||||||
|
getPrimaryLanguage,
|
||||||
|
isLikelyDatabaseRoot,
|
||||||
|
ProgressCallback,
|
||||||
|
withProgress
|
||||||
|
} from './helpers';
|
||||||
import { zipArchiveScheme, encodeArchiveBasePath, decodeSourceArchiveUri, encodeSourceArchiveUri } from './archive-filesystem-provider';
|
import { zipArchiveScheme, encodeArchiveBasePath, decodeSourceArchiveUri, encodeSourceArchiveUri } from './archive-filesystem-provider';
|
||||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||||
import { Logger, logger } from './logging';
|
import { Logger, logger } from './logging';
|
||||||
@@ -37,11 +45,13 @@ export interface DatabaseOptions {
|
|||||||
displayName?: string;
|
displayName?: string;
|
||||||
ignoreSourceArchive?: boolean;
|
ignoreSourceArchive?: boolean;
|
||||||
dateAdded?: number | undefined;
|
dateAdded?: number | undefined;
|
||||||
|
language?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FullDatabaseOptions extends DatabaseOptions {
|
export interface FullDatabaseOptions extends DatabaseOptions {
|
||||||
ignoreSourceArchive: boolean;
|
ignoreSourceArchive: boolean;
|
||||||
dateAdded: number | undefined;
|
dateAdded: number | undefined;
|
||||||
|
language: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PersistedDatabaseItem {
|
interface PersistedDatabaseItem {
|
||||||
@@ -194,6 +204,9 @@ export interface DatabaseItem {
|
|||||||
readonly databaseUri: vscode.Uri;
|
readonly databaseUri: vscode.Uri;
|
||||||
/** The name of the database to be displayed in the UI */
|
/** The name of the database to be displayed in the UI */
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
/** The primary language of the database or empty string if unknown */
|
||||||
|
readonly language: string;
|
||||||
/** The URI of the database's source archive, or `undefined` if no source archive is to be used. */
|
/** The URI of the database's source archive, or `undefined` if no source archive is to be used. */
|
||||||
readonly sourceArchive: vscode.Uri | undefined;
|
readonly sourceArchive: vscode.Uri | undefined;
|
||||||
/**
|
/**
|
||||||
@@ -433,6 +446,10 @@ export class DatabaseItemImpl implements DatabaseItem {
|
|||||||
return dbInfo.datasetFolder;
|
return dbInfo.datasetFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get language() {
|
||||||
|
return this.options.language || '';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the root uri of the virtual filesystem for this database's source archive.
|
* Returns the root uri of the virtual filesystem for this database's source archive.
|
||||||
*/
|
*/
|
||||||
@@ -502,18 +519,16 @@ export class DatabaseManager extends DisposableObject {
|
|||||||
progress: ProgressCallback,
|
progress: ProgressCallback,
|
||||||
token: vscode.CancellationToken,
|
token: vscode.CancellationToken,
|
||||||
uri: vscode.Uri,
|
uri: vscode.Uri,
|
||||||
options?: DatabaseOptions
|
|
||||||
): Promise<DatabaseItem> {
|
): Promise<DatabaseItem> {
|
||||||
|
|
||||||
const contents = await resolveDatabaseContents(uri);
|
const contents = await resolveDatabaseContents(uri);
|
||||||
const realOptions = options || {};
|
|
||||||
// Ignore the source archive for QLTest databases by default.
|
// Ignore the source archive for QLTest databases by default.
|
||||||
const isQLTestDatabase = path.extname(uri.fsPath) === '.testproj';
|
const isQLTestDatabase = path.extname(uri.fsPath) === '.testproj';
|
||||||
const fullOptions: FullDatabaseOptions = {
|
const fullOptions: FullDatabaseOptions = {
|
||||||
ignoreSourceArchive: (realOptions.ignoreSourceArchive !== undefined) ?
|
ignoreSourceArchive: isQLTestDatabase,
|
||||||
realOptions.ignoreSourceArchive : isQLTestDatabase,
|
// displayName is only set if a user explicitly renames a database
|
||||||
displayName: realOptions.displayName,
|
displayName: undefined,
|
||||||
dateAdded: realOptions.dateAdded || Date.now()
|
dateAdded: Date.now(),
|
||||||
|
language: await getPrimaryLanguage(uri.fsPath)
|
||||||
};
|
};
|
||||||
const databaseItem = new DatabaseItemImpl(uri, contents, fullOptions, (event) => {
|
const databaseItem = new DatabaseItemImpl(uri, contents, fullOptions, (event) => {
|
||||||
this._onDidChangeDatabaseItem.fire(event);
|
this._onDidChangeDatabaseItem.fire(event);
|
||||||
@@ -573,6 +588,7 @@ export class DatabaseManager extends DisposableObject {
|
|||||||
let displayName: string | undefined = undefined;
|
let displayName: string | undefined = undefined;
|
||||||
let ignoreSourceArchive = false;
|
let ignoreSourceArchive = false;
|
||||||
let dateAdded = undefined;
|
let dateAdded = undefined;
|
||||||
|
let language = '';
|
||||||
if (state.options) {
|
if (state.options) {
|
||||||
if (typeof state.options.displayName === 'string') {
|
if (typeof state.options.displayName === 'string') {
|
||||||
displayName = state.options.displayName;
|
displayName = state.options.displayName;
|
||||||
@@ -583,11 +599,16 @@ export class DatabaseManager extends DisposableObject {
|
|||||||
if (typeof state.options.dateAdded === 'number') {
|
if (typeof state.options.dateAdded === 'number') {
|
||||||
dateAdded = state.options.dateAdded;
|
dateAdded = state.options.dateAdded;
|
||||||
}
|
}
|
||||||
|
if (state.options.language) {
|
||||||
|
language = state.options.language;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fullOptions: FullDatabaseOptions = {
|
const fullOptions: FullDatabaseOptions = {
|
||||||
ignoreSourceArchive,
|
ignoreSourceArchive,
|
||||||
displayName,
|
displayName,
|
||||||
dateAdded
|
dateAdded,
|
||||||
|
language
|
||||||
};
|
};
|
||||||
const item = new DatabaseItemImpl(vscode.Uri.parse(state.uri, true), undefined, fullOptions,
|
const item = new DatabaseItemImpl(vscode.Uri.parse(state.uri, true), undefined, fullOptions,
|
||||||
(event) => {
|
(event) => {
|
||||||
@@ -815,23 +836,3 @@ export function getUpgradesDirectories(scripts: string[]): vscode.Uri[] {
|
|||||||
const uniqueParentDirs = new Set(parentDirs);
|
const uniqueParentDirs = new Set(parentDirs);
|
||||||
return Array.from(uniqueParentDirs).map(filePath => vscode.Uri.file(filePath));
|
return Array.from(uniqueParentDirs).map(filePath => vscode.Uri.file(filePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: Get the list of supported languages from a list that will be auto-updated.
|
|
||||||
|
|
||||||
export async function isLikelyDatabaseRoot(fsPath: string) {
|
|
||||||
const [a, b, c] = (await Promise.all([
|
|
||||||
// databases can have either .dbinfo or codeql-database.yml.
|
|
||||||
fs.pathExists(path.join(fsPath, '.dbinfo')),
|
|
||||||
fs.pathExists(path.join(fsPath, 'codeql-database.yml')),
|
|
||||||
|
|
||||||
// they *must* have a db-language folder
|
|
||||||
(await fs.readdir(fsPath)).some(isLikelyDbLanguageFolder)
|
|
||||||
]));
|
|
||||||
|
|
||||||
return (a || b) && c;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isLikelyDbLanguageFolder(dbPath: string) {
|
|
||||||
return !!path.basename(dbPath).startsWith('db-');
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -358,12 +358,6 @@ function createRateLimitedResult(): RateLimitedResult {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type DatasetFolderInfo = {
|
|
||||||
dbscheme: string;
|
|
||||||
qlpack: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getQlPackForDbscheme(cliServer: CodeQLCliServer, dbschemePath: string): Promise<string> {
|
export async function getQlPackForDbscheme(cliServer: CodeQLCliServer, dbschemePath: string): Promise<string> {
|
||||||
const qlpacks = await cliServer.resolveQlpacks(getOnDiskWorkspaceFolders());
|
const qlpacks = await cliServer.resolveQlpacks(getOnDiskWorkspaceFolders());
|
||||||
const packs: { packDir: string | undefined; packName: string }[] =
|
const packs: { packDir: string | undefined; packName: string }[] =
|
||||||
@@ -391,7 +385,7 @@ export async function getQlPackForDbscheme(cliServer: CodeQLCliServer, dbschemeP
|
|||||||
throw new Error(`Could not find qlpack file for dbscheme ${dbschemePath}`);
|
throw new Error(`Could not find qlpack file for dbscheme ${dbschemePath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resolveDatasetFolder(cliServer: CodeQLCliServer, datasetFolder: string): Promise<DatasetFolderInfo> {
|
export async function getPrimaryDbscheme(datasetFolder: string): Promise<string> {
|
||||||
const dbschemes = await glob(path.join(datasetFolder, '*.dbscheme'));
|
const dbschemes = await glob(path.join(datasetFolder, '*.dbscheme'));
|
||||||
|
|
||||||
if (dbschemes.length < 1) {
|
if (dbschemes.length < 1) {
|
||||||
@@ -400,12 +394,11 @@ export async function resolveDatasetFolder(cliServer: CodeQLCliServer, datasetFo
|
|||||||
|
|
||||||
dbschemes.sort();
|
dbschemes.sort();
|
||||||
const dbscheme = dbschemes[0];
|
const dbscheme = dbschemes[0];
|
||||||
|
|
||||||
if (dbschemes.length > 1) {
|
if (dbschemes.length > 1) {
|
||||||
Window.showErrorMessage(`Found multiple dbschemes in ${datasetFolder} during quick query; arbitrarily choosing the first, ${dbscheme}, to decide what library to use.`);
|
Window.showErrorMessage(`Found multiple dbschemes in ${datasetFolder} during quick query; arbitrarily choosing the first, ${dbscheme}, to decide what library to use.`);
|
||||||
}
|
}
|
||||||
|
return dbscheme;
|
||||||
const qlpack = await getQlPackForDbscheme(cliServer, dbscheme);
|
|
||||||
return { dbscheme, qlpack };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -464,3 +457,71 @@ export class CachedOperation<U> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The following functions al heuristically determine metadata about databases.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const dbSchemeToLanguage = {
|
||||||
|
'semmlecode.javascript.dbscheme': 'javascript',
|
||||||
|
'semmlecode.cpp.dbscheme': 'cpp',
|
||||||
|
'semmlecode.dbscheme': 'java',
|
||||||
|
'semmlecode.python.dbscheme': 'python',
|
||||||
|
'semmlecode.csharp.dbscheme': 'csharp',
|
||||||
|
'go.dbscheme': 'go'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the initial contents for an empty query, based on the language of the selected
|
||||||
|
* databse.
|
||||||
|
*
|
||||||
|
* First try to get the contents text based on language. if that fails, try to get based on
|
||||||
|
* dbscheme. Otherwise return no import statement.
|
||||||
|
*
|
||||||
|
* @param language the database language or empty string if unknown
|
||||||
|
* @param dbscheme path to the dbscheme file
|
||||||
|
*
|
||||||
|
* @returns an import and empty select statement appropriate for the selected language
|
||||||
|
*/
|
||||||
|
export function getInitialQueryContents(language: string, dbscheme: string) {
|
||||||
|
if (!language) {
|
||||||
|
const dbschemeBase = path.basename(dbscheme) as keyof typeof dbSchemeToLanguage;
|
||||||
|
language = dbSchemeToLanguage[dbschemeBase];
|
||||||
|
}
|
||||||
|
|
||||||
|
return language
|
||||||
|
? `import ${language}\n\nselect ""`
|
||||||
|
: 'select ""';
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function isLikelyDatabaseRoot(maybeRoot: string) {
|
||||||
|
const [a, b, c] = (await Promise.all([
|
||||||
|
// databases can have either .dbinfo or codeql-database.yml.
|
||||||
|
fs.pathExists(path.join(maybeRoot, '.dbinfo')),
|
||||||
|
fs.pathExists(path.join(maybeRoot, 'codeql-database.yml')),
|
||||||
|
|
||||||
|
// they *must* have a db-{language} folder
|
||||||
|
glob('db-*/', { cwd: maybeRoot })
|
||||||
|
]));
|
||||||
|
|
||||||
|
return !!((a || b) && c);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLikelyDbLanguageFolder(dbPath: string) {
|
||||||
|
return !!path.basename(dbPath).startsWith('db-');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPrimaryLanguage(root: string) {
|
||||||
|
try {
|
||||||
|
const metadataFile = path.join(root, 'codeql-database.yml');
|
||||||
|
if (await fs.pathExists(metadataFile)) {
|
||||||
|
const metadata = yaml.safeLoad(await fs.readFile(metadataFile, 'utf8')) as { primaryLanguage: string | undefined };
|
||||||
|
return metadata.primaryLanguage || '';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// could not determine language
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* helpers-pure.ts
|
* helpers-pure.ts
|
||||||
* ------------
|
* ------------
|
||||||
*
|
*
|
||||||
* Helper functions that don't depend on vscode and therefore can be used by the front-end and pure unit tests.
|
* Helper functions that don't depend on vscode or the CLI and therefore can be used by the front-end and pure unit tests.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,8 +5,16 @@ import { CancellationToken, ExtensionContext, window as Window, workspace, Uri }
|
|||||||
import { ErrorCodes, ResponseError } from 'vscode-languageclient';
|
import { ErrorCodes, ResponseError } from 'vscode-languageclient';
|
||||||
import { CodeQLCliServer } from './cli';
|
import { CodeQLCliServer } from './cli';
|
||||||
import { DatabaseUI } from './databases-ui';
|
import { DatabaseUI } from './databases-ui';
|
||||||
import * as helpers from './helpers';
|
|
||||||
import { logger } from './logging';
|
import { logger } from './logging';
|
||||||
|
import {
|
||||||
|
getInitialQueryContents,
|
||||||
|
getPrimaryDbscheme,
|
||||||
|
getQlPackForDbscheme,
|
||||||
|
ProgressCallback,
|
||||||
|
showAndLogErrorMessage,
|
||||||
|
showBinaryChoiceDialog,
|
||||||
|
UserCancellationException
|
||||||
|
} from './helpers';
|
||||||
|
|
||||||
const QUICK_QUERIES_DIR_NAME = 'quick-queries';
|
const QUICK_QUERIES_DIR_NAME = 'quick-queries';
|
||||||
const QUICK_QUERY_QUERY_NAME = 'quick-query.ql';
|
const QUICK_QUERY_QUERY_NAME = 'quick-query.ql';
|
||||||
@@ -16,21 +24,6 @@ export function isQuickQueryPath(queryPath: string): boolean {
|
|||||||
return path.basename(queryPath) === QUICK_QUERY_QUERY_NAME;
|
return path.basename(queryPath) === QUICK_QUERY_QUERY_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* `getBaseText` heuristically returns an appropriate import statement
|
|
||||||
* prelude based on the filename of the dbscheme file given. TODO: add
|
|
||||||
* a 'default import' field to the qlpack itself, and use that.
|
|
||||||
*/
|
|
||||||
function getBaseText(dbschemeBase: string) {
|
|
||||||
if (dbschemeBase == 'semmlecode.javascript.dbscheme') return 'import javascript\n\nselect ""';
|
|
||||||
if (dbschemeBase == 'semmlecode.cpp.dbscheme') return 'import cpp\n\nselect ""';
|
|
||||||
if (dbschemeBase == 'semmlecode.dbscheme') return 'import java\n\nselect ""';
|
|
||||||
if (dbschemeBase == 'semmlecode.python.dbscheme') return 'import python\n\nselect ""';
|
|
||||||
if (dbschemeBase == 'semmlecode.csharp.dbscheme') return 'import csharp\n\nselect ""';
|
|
||||||
if (dbschemeBase == 'go.dbscheme') return 'import go\n\nselect ""';
|
|
||||||
return 'select ""';
|
|
||||||
}
|
|
||||||
|
|
||||||
function getQuickQueriesDir(ctx: ExtensionContext): string {
|
function getQuickQueriesDir(ctx: ExtensionContext): string {
|
||||||
const storagePath = ctx.storagePath;
|
const storagePath = ctx.storagePath;
|
||||||
if (storagePath === undefined) {
|
if (storagePath === undefined) {
|
||||||
@@ -51,7 +44,7 @@ export async function displayQuickQuery(
|
|||||||
ctx: ExtensionContext,
|
ctx: ExtensionContext,
|
||||||
cliServer: CodeQLCliServer,
|
cliServer: CodeQLCliServer,
|
||||||
databaseUI: DatabaseUI,
|
databaseUI: DatabaseUI,
|
||||||
progress: helpers.ProgressCallback,
|
progress: ProgressCallback,
|
||||||
token: CancellationToken
|
token: CancellationToken
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -85,7 +78,7 @@ export async function displayQuickQuery(
|
|||||||
// being undefined) just let the user know that they're in for a
|
// being undefined) just let the user know that they're in for a
|
||||||
// restart.
|
// restart.
|
||||||
if (workspace.workspaceFile === undefined) {
|
if (workspace.workspaceFile === undefined) {
|
||||||
const makeMultiRoot = await helpers.showBinaryChoiceDialog('Quick query requires multiple folders in the workspace. Reload workspace as multi-folder workspace?');
|
const makeMultiRoot = await showBinaryChoiceDialog('Quick query requires multiple folders in the workspace. Reload workspace as multi-folder workspace?');
|
||||||
if (makeMultiRoot) {
|
if (makeMultiRoot) {
|
||||||
updateQuickQueryDir(queriesDir, workspaceFolders.length, 0);
|
updateQuickQueryDir(queriesDir, workspaceFolders.length, 0);
|
||||||
}
|
}
|
||||||
@@ -105,7 +98,9 @@ export async function displayQuickQuery(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const datasetFolder = await dbItem.getDatasetFolder(cliServer);
|
const datasetFolder = await dbItem.getDatasetFolder(cliServer);
|
||||||
const { qlpack, dbscheme } = await helpers.resolveDatasetFolder(cliServer, datasetFolder);
|
const dbscheme = await getPrimaryDbscheme(datasetFolder);
|
||||||
|
const qlpack = await getQlPackForDbscheme(cliServer, dbscheme);
|
||||||
|
|
||||||
const quickQueryQlpackYaml: any = {
|
const quickQueryQlpackYaml: any = {
|
||||||
name: 'quick-query',
|
name: 'quick-query',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
@@ -114,21 +109,21 @@ export async function displayQuickQuery(
|
|||||||
|
|
||||||
const qlFile = path.join(queriesDir, QUICK_QUERY_QUERY_NAME);
|
const qlFile = path.join(queriesDir, QUICK_QUERY_QUERY_NAME);
|
||||||
const qlPackFile = path.join(queriesDir, 'qlpack.yml');
|
const qlPackFile = path.join(queriesDir, 'qlpack.yml');
|
||||||
await fs.writeFile(qlFile, getBaseText(path.basename(dbscheme)), 'utf8');
|
await fs.writeFile(qlFile, getInitialQueryContents(dbItem.language, dbscheme), 'utf8');
|
||||||
await fs.writeFile(qlPackFile, yaml.safeDump(quickQueryQlpackYaml), 'utf8');
|
await fs.writeFile(qlPackFile, yaml.safeDump(quickQueryQlpackYaml), 'utf8');
|
||||||
Window.showTextDocument(await workspace.openTextDocument(qlFile));
|
Window.showTextDocument(await workspace.openTextDocument(qlFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: clean up error handling for top-level commands like this
|
// TODO: clean up error handling for top-level commands like this
|
||||||
catch (e) {
|
catch (e) {
|
||||||
if (e instanceof helpers.UserCancellationException) {
|
if (e instanceof UserCancellationException) {
|
||||||
logger.log(e.message);
|
logger.log(e.message);
|
||||||
}
|
}
|
||||||
else if (e instanceof ResponseError && e.code == ErrorCodes.RequestCancelled) {
|
else if (e instanceof ResponseError && e.code == ErrorCodes.RequestCancelled) {
|
||||||
logger.log(e.message);
|
logger.log(e.message);
|
||||||
}
|
}
|
||||||
else if (e instanceof Error)
|
else if (e instanceof Error)
|
||||||
helpers.showAndLogErrorMessage(e.message);
|
showAndLogErrorMessage(e.message);
|
||||||
else
|
else
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,20 +12,20 @@ import {
|
|||||||
DatabaseManager,
|
DatabaseManager,
|
||||||
DatabaseItemImpl,
|
DatabaseItemImpl,
|
||||||
DatabaseContents,
|
DatabaseContents,
|
||||||
isLikelyDbLanguageFolder,
|
|
||||||
FullDatabaseOptions
|
FullDatabaseOptions
|
||||||
} from '../../databases';
|
} from '../../databases';
|
||||||
import { Logger } from '../../logging';
|
import { Logger } from '../../logging';
|
||||||
import { encodeArchiveBasePath, encodeSourceArchiveUri } from '../../archive-filesystem-provider';
|
import { encodeArchiveBasePath, encodeSourceArchiveUri } from '../../archive-filesystem-provider';
|
||||||
import { QueryServerClient } from '../../queryserver-client';
|
import { QueryServerClient } from '../../queryserver-client';
|
||||||
import { ProgressCallback } from '../../helpers';
|
|
||||||
import { registerDatabases } from '../../pure/messages';
|
import { registerDatabases } from '../../pure/messages';
|
||||||
|
import { isLikelyDbLanguageFolder, ProgressCallback } from '../../helpers';
|
||||||
|
|
||||||
describe('databases', () => {
|
describe('databases', () => {
|
||||||
|
|
||||||
const MOCK_DB_OPTIONS: FullDatabaseOptions = {
|
const MOCK_DB_OPTIONS: FullDatabaseOptions = {
|
||||||
dateAdded: 123,
|
dateAdded: 123,
|
||||||
ignoreSourceArchive: false
|
ignoreSourceArchive: false,
|
||||||
|
language: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
let databaseManager: DatabaseManager;
|
let databaseManager: DatabaseManager;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const expect = chai.expect;
|
|||||||
describe('queryResolver', () => {
|
describe('queryResolver', () => {
|
||||||
let module: Record<string, Function>;
|
let module: Record<string, Function>;
|
||||||
let writeFileSpy: sinon.SinonSpy;
|
let writeFileSpy: sinon.SinonSpy;
|
||||||
let resolveDatasetFolderSpy: sinon.SinonStub;
|
let getQlPackForDbschemeSpy: sinon.SinonStub;
|
||||||
let mockCli: Record<string, sinon.SinonStub>;
|
let mockCli: Record<string, sinon.SinonStub>;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockCli = {
|
mockCli = {
|
||||||
@@ -60,7 +60,7 @@ describe('queryResolver', () => {
|
|||||||
|
|
||||||
describe('qlpackOfDatabase', () => {
|
describe('qlpackOfDatabase', () => {
|
||||||
it('should get the qlpack of a database', async () => {
|
it('should get the qlpack of a database', async () => {
|
||||||
resolveDatasetFolderSpy.returns({ qlpack: 'my-qlpack' });
|
getQlPackForDbschemeSpy.resolves('my-qlpack');
|
||||||
const db = {
|
const db = {
|
||||||
contents: {
|
contents: {
|
||||||
datasetUri: {
|
datasetUri: {
|
||||||
@@ -70,20 +70,20 @@ describe('queryResolver', () => {
|
|||||||
};
|
};
|
||||||
const result = await module.qlpackOfDatabase(mockCli, db);
|
const result = await module.qlpackOfDatabase(mockCli, db);
|
||||||
expect(result).to.eq('my-qlpack');
|
expect(result).to.eq('my-qlpack');
|
||||||
expect(resolveDatasetFolderSpy).to.have.been.calledWith(mockCli, '/path/to/database');
|
expect(getQlPackForDbschemeSpy).to.have.been.calledWith(mockCli, '/path/to/database');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createModule() {
|
function createModule() {
|
||||||
writeFileSpy = sinon.spy();
|
writeFileSpy = sinon.spy();
|
||||||
resolveDatasetFolderSpy = sinon.stub();
|
getQlPackForDbschemeSpy = sinon.stub();
|
||||||
return proxyquire('../../../contextual/queryResolver', {
|
return proxyquire('../../../contextual/queryResolver', {
|
||||||
'fs-extra': {
|
'fs-extra': {
|
||||||
writeFile: writeFileSpy
|
writeFile: writeFileSpy
|
||||||
},
|
},
|
||||||
|
|
||||||
'../helpers': {
|
'../helpers': {
|
||||||
resolveDatasetFolder: resolveDatasetFolderSpy,
|
getQlPackForDbscheme: getQlPackForDbschemeSpy,
|
||||||
getOnDiskWorkspaceFolders: () => ({}),
|
getOnDiskWorkspaceFolders: () => ({}),
|
||||||
showAndLogErrorMessage: () => ({})
|
showAndLogErrorMessage: () => ({})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { ExtensionContext, Memento } from 'vscode';
|
import { ExtensionContext, Memento } from 'vscode';
|
||||||
import { InvocationRateLimiter } from '../../helpers';
|
import * as yaml from 'js-yaml';
|
||||||
|
import * as tmp from 'tmp';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs-extra';
|
||||||
|
|
||||||
|
import { getInitialQueryContents, getPrimaryLanguage, InvocationRateLimiter } from '../../helpers';
|
||||||
|
|
||||||
describe('Invocation rate limiter', () => {
|
describe('Invocation rate limiter', () => {
|
||||||
// 1 January 2020
|
// 1 January 2020
|
||||||
@@ -86,6 +91,41 @@ describe('Invocation rate limiter', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('codeql-database.yml tests', () => {
|
||||||
|
let dir: tmp.DirResult;
|
||||||
|
beforeEach(() => {
|
||||||
|
dir = tmp.dirSync();
|
||||||
|
const contents = yaml.safeDump({
|
||||||
|
primaryLanguage: 'cpp'
|
||||||
|
});
|
||||||
|
fs.writeFileSync(path.join(dir.name, 'codeql-database.yml'), contents, 'utf8');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
dir.removeCallback();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get the language of a database', async () => {
|
||||||
|
expect(await getPrimaryLanguage(dir.name)).to.eq('cpp');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get the language of a database when langauge is not known', async () => {
|
||||||
|
expect(await getPrimaryLanguage('xxx')).to.eq('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get initial query contents when language is known', () => {
|
||||||
|
expect(getInitialQueryContents('cpp', 'hucairz')).to.eq('import cpp\n\nselect ""');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get initial query contents when dbscheme is known', () => {
|
||||||
|
expect(getInitialQueryContents('', 'semmlecode.cpp.dbscheme')).to.eq('import cpp\n\nselect ""');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get initial query contents when nothing is known', () => {
|
||||||
|
expect(getInitialQueryContents('', 'hucairz')).to.eq('select ""');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
class MockExtensionContext implements ExtensionContext {
|
class MockExtensionContext implements ExtensionContext {
|
||||||
subscriptions: { dispose(): unknown }[] = [];
|
subscriptions: { dispose(): unknown }[] = [];
|
||||||
workspaceState: Memento = new MockMemento();
|
workspaceState: Memento = new MockMemento();
|
||||||
|
|||||||
Reference in New Issue
Block a user