Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a language label next to databases in the UI #697

Merged
merged 3 commits into from
Dec 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/ql-vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
- 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)
- 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

Expand Down
16 changes: 15 additions & 1 deletion extensions/ql-vscode/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface DbInfo {
sourceArchiveRoot: string;
datasetFolder: string;
logsFolder: string;
languages: string[];
}

/**
Expand Down Expand Up @@ -123,6 +124,11 @@ export class CodeQLCliServer implements Disposable {
*/
private static CLI_VERSION_WITH_DECOMPILE_KIND_DIL = new SemVer('2.3.0');

/**
* CLI version where languages are exposed during a `codeql resolve database` command.
*/
private static CLI_VERSION_WITH_LANGUAGE = new SemVer('2.4.1');
adityasharad marked this conversation as resolved.
Show resolved Hide resolved

/** The process for the cli server, or undefined if one doesn't exist yet */
process?: child_process.ChildProcessWithoutNullStreams;
/** Queue of future commands*/
Expand Down Expand Up @@ -689,7 +695,7 @@ export class CodeQLCliServer implements Disposable {
}

async generateDil(qloFile: string, outFile: string): Promise<void> {
const extraArgs = (await this.getVersion()).compare(CodeQLCliServer.CLI_VERSION_WITH_DECOMPILE_KIND_DIL) >= 0
const extraArgs = await this.supportsDecompileDil()
? ['--kind', 'dil', '-o', outFile, qloFile]
: ['-o', outFile, qloFile];
await this.runCodeQlCliCommand(
Expand All @@ -706,6 +712,14 @@ export class CodeQLCliServer implements Disposable {
return this._version;
}

private async supportsDecompileDil() {
return (await this.getVersion()).compare(CodeQLCliServer.CLI_VERSION_WITH_DECOMPILE_KIND_DIL) >= 0;
}

public async supportsLanguageName() {
return (await this.getVersion()).compare(CodeQLCliServer.CLI_VERSION_WITH_LANGUAGE) >= 0;
}

private async refreshVersion() {
const distribution = await this.distributionProvider.getDistribution();
switch (distribution.kind) {
Expand Down
3 changes: 1 addition & 2 deletions extensions/ql-vscode/src/contextual/queryResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ export async function qlpackOfDatabase(cli: CodeQLCliServer, db: DatabaseItem):
if (db.contents === undefined)
return undefined;
const datasetPath = db.contents.datasetUri.fsPath;
const { qlpack } = await helpers.resolveDatasetFolder(cli, datasetPath);
return qlpack;
return await helpers.getQlPackForDbscheme(cli, datasetPath);
}


Expand Down
7 changes: 4 additions & 3 deletions extensions/ql-vscode/src/databases-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ import {
DatabaseItem,
DatabaseManager,
getUpgradesDirectories,
isLikelyDatabaseRoot,
isLikelyDbLanguageFolder,
} from './databases';
import {
commandRunner,
commandRunnerWithProgress,
getOnDiskWorkspaceFolders,
ProgressCallback,
showAndLogErrorMessage
showAndLogErrorMessage,
isLikelyDatabaseRoot,
isLikelyDbLanguageFolder
} from './helpers';
import { logger } from './logging';
import { clearCacheInDatabase } from './run-queries';
Expand Down Expand Up @@ -143,6 +143,7 @@ class DatabaseTreeDataProvider extends DisposableObject
);
}
item.tooltip = element.databaseUri.fsPath;
item.description = element.language;
return item;
}

Expand Down
78 changes: 47 additions & 31 deletions extensions/ql-vscode/src/databases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import * as path from 'path';
import * as vscode from 'vscode';
import * as cli from './cli';
import { ExtensionContext } from 'vscode';
import { showAndLogErrorMessage, showAndLogWarningMessage, showAndLogInformationMessage, ProgressCallback, withProgress } from './helpers';
import {
showAndLogErrorMessage,
showAndLogWarningMessage,
showAndLogInformationMessage,
isLikelyDatabaseRoot,
ProgressCallback,
withProgress
} from './helpers';
import { zipArchiveScheme, encodeArchiveBasePath, decodeSourceArchiveUri, encodeSourceArchiveUri } from './archive-filesystem-provider';
import { DisposableObject } from './vscode-utils/disposable-object';
import { Logger, logger } from './logging';
Expand Down Expand Up @@ -37,11 +44,13 @@ export interface DatabaseOptions {
displayName?: string;
ignoreSourceArchive?: boolean;
dateAdded?: number | undefined;
language?: string;
}

export interface FullDatabaseOptions extends DatabaseOptions {
ignoreSourceArchive: boolean;
dateAdded: number | undefined;
language: string | undefined;
}

interface PersistedDatabaseItem {
Expand Down Expand Up @@ -194,6 +203,9 @@ export interface DatabaseItem {
readonly databaseUri: vscode.Uri;
/** The name of the database to be displayed in the UI */
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. */
readonly sourceArchive: vscode.Uri | undefined;
/**
Expand Down Expand Up @@ -433,6 +445,10 @@ export class DatabaseItemImpl implements DatabaseItem {
return dbInfo.datasetFolder;
}

public get language() {
return this.options.language || '';
}

/**
* Returns the root uri of the virtual filesystem for this database's source archive.
*/
Expand Down Expand Up @@ -491,7 +507,8 @@ export class DatabaseManager extends DisposableObject {
constructor(
private readonly ctx: ExtensionContext,
private readonly qs: QueryServerClient,
public readonly logger: Logger
private readonly cli: cli.CodeQLCliServer,
public logger: Logger
) {
super();

Expand All @@ -502,18 +519,16 @@ export class DatabaseManager extends DisposableObject {
progress: ProgressCallback,
token: vscode.CancellationToken,
uri: vscode.Uri,
options?: DatabaseOptions
): Promise<DatabaseItem> {

const contents = await resolveDatabaseContents(uri);
const realOptions = options || {};
adityasharad marked this conversation as resolved.
Show resolved Hide resolved
// Ignore the source archive for QLTest databases by default.
const isQLTestDatabase = path.extname(uri.fsPath) === '.testproj';
const fullOptions: FullDatabaseOptions = {
ignoreSourceArchive: (realOptions.ignoreSourceArchive !== undefined) ?
realOptions.ignoreSourceArchive : isQLTestDatabase,
displayName: realOptions.displayName,
dateAdded: realOptions.dateAdded || Date.now()
ignoreSourceArchive: isQLTestDatabase,
// displayName is only set if a user explicitly renames a database
displayName: undefined,
dateAdded: Date.now(),
language: await this.getPrimaryLanguage(uri.fsPath)
};
const databaseItem = new DatabaseItemImpl(uri, contents, fullOptions, (event) => {
this._onDidChangeDatabaseItem.fire(event);
Expand Down Expand Up @@ -573,6 +588,7 @@ export class DatabaseManager extends DisposableObject {
let displayName: string | undefined = undefined;
let ignoreSourceArchive = false;
let dateAdded = undefined;
let language = undefined;
if (state.options) {
if (typeof state.options.displayName === 'string') {
displayName = state.options.displayName;
Expand All @@ -583,13 +599,22 @@ export class DatabaseManager extends DisposableObject {
if (typeof state.options.dateAdded === 'number') {
dateAdded = state.options.dateAdded;
}
language = state.options.language;
}

const dbBaseUri = vscode.Uri.parse(state.uri, true);
if (language === undefined) {
// we haven't been successful yet at getting the language. try again
language = await this.getPrimaryLanguage(dbBaseUri.fsPath);
}

const fullOptions: FullDatabaseOptions = {
ignoreSourceArchive,
displayName,
dateAdded
dateAdded,
language
};
const item = new DatabaseItemImpl(vscode.Uri.parse(state.uri, true), undefined, fullOptions,
const item = new DatabaseItemImpl(dbBaseUri, undefined, fullOptions,
(event) => {
this._onDidChangeDatabaseItem.fire(event);
});
Expand Down Expand Up @@ -804,6 +829,17 @@ export class DatabaseManager extends DisposableObject {
}
return false;
}

private async getPrimaryLanguage(dbPath: string) {
if (!(await this.cli.supportsLanguageName())) {
// return undefined so that we recalculate on restart until the cli is at a version that
// supports this feature. This recalculation is cheap since we avoid calling into the cli
// unless we know it can return the langauges property.
return undefined;
}
const dbInfo = await this.cli.resolveDatabase(dbPath);
return dbInfo.languages?.[0] || '';
}
}

/**
Expand All @@ -815,23 +851,3 @@ export function getUpgradesDirectories(scripts: string[]): vscode.Uri[] {
const uniqueParentDirs = new Set(parentDirs);
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-');
}
2 changes: 1 addition & 1 deletion extensions/ql-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ async function activateWithInstalledDistribution(
await qs.startQueryServer();

logger.log('Initializing database manager.');
const dbm = new DatabaseManager(ctx, qs, logger);
const dbm = new DatabaseManager(ctx, qs, cliServer, logger);
ctx.subscriptions.push(dbm);
logger.log('Initializing database panel.');
const databaseUI = new DatabaseUI(
Expand Down
84 changes: 74 additions & 10 deletions extensions/ql-vscode/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,12 +358,6 @@ function createRateLimitedResult(): RateLimitedResult {
};
}


export type DatasetFolderInfo = {
dbscheme: string;
qlpack: string;
}

export async function getQlPackForDbscheme(cliServer: CodeQLCliServer, dbschemePath: string): Promise<string> {
const qlpacks = await cliServer.resolveQlpacks(getOnDiskWorkspaceFolders());
const packs: { packDir: string | undefined; packName: string }[] =
Expand Down Expand Up @@ -391,7 +385,7 @@ export async function getQlPackForDbscheme(cliServer: CodeQLCliServer, dbschemeP
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'));

if (dbschemes.length < 1) {
Expand All @@ -400,12 +394,11 @@ export async function resolveDatasetFolder(cliServer: CodeQLCliServer, datasetFo

dbschemes.sort();
const dbscheme = dbschemes[0];

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.`);
}

const qlpack = await getQlPackForDbscheme(cliServer, dbscheme);
return { dbscheme, qlpack };
return dbscheme;
}

/**
Expand Down Expand Up @@ -464,3 +457,74 @@ export class CachedOperation<U> {
}
}
}



/**
* The following functions al heuristically determine metadata about databases.
*/

/**
* Note that this heuristic is only being used for backwards compatibility with
* CLI versions before the langauge name was introduced to dbInfo. Features
* that do not require backwards compatibility should call
* `cli.CodeQLCliServer.resolveDatabase` and use the first entry in the
* `languages` property.
*
* @see cli.CodeQLCliServer.supportsLanguageName
* @see cli.CodeQLCliServer.resolveDatabase
*/
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 use the given language name. If that doesn't exist, try to infer it 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 ""';
}

/**
* Heuristically determines if the directory passed in corresponds
* to a database root.
*
* @param maybeRoot
*/
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-');
}
2 changes: 1 addition & 1 deletion extensions/ql-vscode/src/pure/helpers-pure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* 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.
*/

/**
Expand Down
Loading