Skip to content

Commit c75044f

Browse files
committed
Use codeql resolve database to get language
This commit moves to using codeql resolve database instead of inspecting the `codeql-database.yml` file. When the extension starts and if the cli supports it, the extension will attempt to get the name for any databases that don't yet have a name. Once a name is searched for once by the cli, it will be cached so we don't need to rediscover the name again.
1 parent 7d86f1b commit c75044f

6 files changed

Lines changed: 91 additions & 37 deletions

File tree

extensions/ql-vscode/src/cli.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export interface DbInfo {
5050
sourceArchiveRoot: string;
5151
datasetFolder: string;
5252
logsFolder: string;
53-
primaryLanguage: string;
53+
languages: string[];
5454
}
5555

5656
/**
@@ -124,6 +124,11 @@ export class CodeQLCliServer implements Disposable {
124124
*/
125125
private static CLI_VERSION_WITH_DECOMPILE_KIND_DIL = new SemVer('2.3.0');
126126

127+
/**
128+
* CLI version where --kind=DIL was introduced
129+
*/
130+
private static CLI_VERSION_WITH_LANGUAGE = new SemVer('2.4.1');
131+
127132
/** The process for the cli server, or undefined if one doesn't exist yet */
128133
process?: child_process.ChildProcessWithoutNullStreams;
129134
/** Queue of future commands*/
@@ -690,7 +695,7 @@ export class CodeQLCliServer implements Disposable {
690695
}
691696

692697
async generateDil(qloFile: string, outFile: string): Promise<void> {
693-
const extraArgs = (await this.getVersion()).compare(CodeQLCliServer.CLI_VERSION_WITH_DECOMPILE_KIND_DIL) >= 0
698+
const extraArgs = await this.supportsDecompileDil()
694699
? ['--kind', 'dil', '-o', outFile, qloFile]
695700
: ['-o', outFile, qloFile];
696701
await this.runCodeQlCliCommand(
@@ -707,6 +712,14 @@ export class CodeQLCliServer implements Disposable {
707712
return this._version;
708713
}
709714

715+
private async supportsDecompileDil() {
716+
return (await this.getVersion()).compare(CodeQLCliServer.CLI_VERSION_WITH_DECOMPILE_KIND_DIL) >= 0;
717+
}
718+
719+
public async supportsLangaugeName() {
720+
return (await this.getVersion()).compare(CodeQLCliServer.CLI_VERSION_WITH_LANGUAGE) >= 0;
721+
}
722+
710723
private async refreshVersion() {
711724
const distribution = await this.distributionProvider.getDistribution();
712725
switch (distribution.kind) {

extensions/ql-vscode/src/databases.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
showAndLogErrorMessage,
99
showAndLogWarningMessage,
1010
showAndLogInformationMessage,
11-
getPrimaryLanguage,
1211
isLikelyDatabaseRoot
1312
} from './helpers';
1413
import { zipArchiveScheme, encodeArchiveBasePath, decodeSourceArchiveUri, encodeSourceArchiveUri } from './archive-filesystem-provider';
@@ -48,7 +47,7 @@ export interface DatabaseOptions {
4847
export interface FullDatabaseOptions extends DatabaseOptions {
4948
ignoreSourceArchive: boolean;
5049
dateAdded: number | undefined;
51-
language: string;
50+
language: string | undefined;
5251
}
5352

5453
interface PersistedDatabaseItem {
@@ -500,6 +499,7 @@ export class DatabaseManager extends DisposableObject {
500499
constructor(
501500
private ctx: ExtensionContext,
502501
public config: QueryServerConfig,
502+
private cli: cli.CodeQLCliServer,
503503
public logger: Logger
504504
) {
505505
super();
@@ -518,7 +518,7 @@ export class DatabaseManager extends DisposableObject {
518518
// displayName is only set if a user explicitly renames a database
519519
displayName: undefined,
520520
dateAdded: Date.now(),
521-
language: await getPrimaryLanguage(uri.fsPath)
521+
language: await this.getPrimaryLanguage(uri.fsPath)
522522
};
523523
const databaseItem = new DatabaseItemImpl(uri, contents, fullOptions, (event) => {
524524
this._onDidChangeDatabaseItem.fire(event);
@@ -575,7 +575,7 @@ export class DatabaseManager extends DisposableObject {
575575
let displayName: string | undefined = undefined;
576576
let ignoreSourceArchive = false;
577577
let dateAdded = undefined;
578-
let language = '';
578+
let language = undefined;
579579
if (state.options) {
580580
if (typeof state.options.displayName === 'string') {
581581
displayName = state.options.displayName;
@@ -586,9 +586,13 @@ export class DatabaseManager extends DisposableObject {
586586
if (typeof state.options.dateAdded === 'number') {
587587
dateAdded = state.options.dateAdded;
588588
}
589-
if (state.options.language) {
590-
language = state.options.language;
591-
}
589+
language = state.options.language;
590+
}
591+
592+
const dbBaseUri = vscode.Uri.parse(state.uri, true);
593+
if (language === undefined) {
594+
// we haven't been successful yet at getting the language. try again
595+
language = await this.getPrimaryLanguage(dbBaseUri.fsPath);
592596
}
593597

594598
const fullOptions: FullDatabaseOptions = {
@@ -597,7 +601,7 @@ export class DatabaseManager extends DisposableObject {
597601
dateAdded,
598602
language
599603
};
600-
const item = new DatabaseItemImpl(vscode.Uri.parse(state.uri, true), undefined, fullOptions,
604+
const item = new DatabaseItemImpl(dbBaseUri, undefined, fullOptions,
601605
(event) => {
602606
this._onDidChangeDatabaseItem.fire(event);
603607
});
@@ -746,6 +750,15 @@ export class DatabaseManager extends DisposableObject {
746750
}
747751
return false;
748752
}
753+
754+
private async getPrimaryLanguage(dbPath: string) {
755+
if (!(await this.cli.supportsLangaugeName())) {
756+
// return undefined so that we continually recalculate until the cli version is bumped
757+
return undefined;
758+
}
759+
const dbInfo = await this.cli.resolveDatabase(dbPath);
760+
return dbInfo.languages?.[0] || '';
761+
}
749762
}
750763

751764
/**

extensions/ql-vscode/src/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ async function activateWithInstalledDistribution(
339339
await qs.startQueryServer();
340340

341341
logger.log('Initializing database manager.');
342-
const dbm = new DatabaseManager(ctx, qlConfigurationListener, logger);
342+
const dbm = new DatabaseManager(ctx, qlConfigurationListener, cliServer, logger);
343343
ctx.subscriptions.push(dbm);
344344
logger.log('Initializing database panel.');
345345
const databaseUI = new DatabaseUI(

extensions/ql-vscode/src/helpers.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,16 @@ export class CachedOperation<U> {
464464
* The following functions al heuristically determine metadata about databases.
465465
*/
466466

467+
/**
468+
* Note that this heuristic is only being used for backwards compatibility with
469+
* CLI versions before the langauge name was introduced to dbInfo. Implementations
470+
* that do not require backwards compatibility should call
471+
* `cli.CodeQLCliServer.resolveDatabase` and use the first entry in the
472+
* `languages` property.
473+
*
474+
* @see cli.CodeQLCliServer.supportsLangaugeName
475+
* @see cli.CodeQLCliServer.resolveDatabase
476+
*/
467477
const dbSchemeToLanguage = {
468478
'semmlecode.javascript.dbscheme': 'javascript',
469479
'semmlecode.cpp.dbscheme': 'cpp',
@@ -496,6 +506,12 @@ export function getInitialQueryContents(language: string, dbscheme: string) {
496506
: 'select ""';
497507
}
498508

509+
/**
510+
* Heuristically determines if the directory passed in corresponds
511+
* to a database root.
512+
*
513+
* @param maybeRoot
514+
*/
499515
export async function isLikelyDatabaseRoot(maybeRoot: string) {
500516
const [a, b, c] = (await Promise.all([
501517
// databases can have either .dbinfo or codeql-database.yml.
@@ -512,16 +528,3 @@ export async function isLikelyDatabaseRoot(maybeRoot: string) {
512528
export function isLikelyDbLanguageFolder(dbPath: string) {
513529
return !!path.basename(dbPath).startsWith('db-');
514530
}
515-
516-
export async function getPrimaryLanguage(root: string) {
517-
try {
518-
const metadataFile = path.join(root, 'codeql-database.yml');
519-
if (await fs.pathExists(metadataFile)) {
520-
const metadata = yaml.safeLoad(await fs.readFile(metadataFile, 'utf8')) as { primaryLanguage: string | undefined };
521-
return metadata.primaryLanguage || '';
522-
}
523-
} catch (e) {
524-
// could not determine language
525-
}
526-
return '';
527-
}

extensions/ql-vscode/src/vscode-tests/minimal-workspace/databases.test.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ import {
1515
FullDatabaseOptions
1616
} from '../../databases';
1717
import { QueryServerConfig } from '../../config';
18+
import { CodeQLCliServer } from '../../cli';
1819
import { Logger } from '../../logging';
1920
import { encodeArchiveBasePath, encodeSourceArchiveUri } from '../../archive-filesystem-provider';
20-
import { isLikelyDbLanguageFolder } from '../../helpers';
2121

2222
describe('databases', () => {
2323

@@ -31,6 +31,8 @@ describe('databases', () => {
3131
let updateSpy: sinon.SinonSpy;
3232
let getSpy: sinon.SinonStub;
3333
let dbChangedHandler: sinon.SinonSpy;
34+
let supportsLanguageNameSpy: sinon.SinonStub;
35+
let resolveDatabaseSpy: sinon.SinonStub;
3436

3537
let sandbox: sinon.SinonSandbox;
3638
let dir: tmp.DirResult;
@@ -44,6 +46,8 @@ describe('databases', () => {
4446
updateSpy = sandbox.spy();
4547
getSpy = sandbox.stub();
4648
dbChangedHandler = sandbox.spy();
49+
supportsLanguageNameSpy = sandbox.stub();
50+
resolveDatabaseSpy = sandbox.stub();
4751
databaseManager = new DatabaseManager(
4852
{
4953
workspaceState: {
@@ -55,6 +59,10 @@ describe('databases', () => {
5559
storagePath: dir.name
5660
} as unknown as ExtensionContext,
5761
{} as QueryServerConfig,
62+
{
63+
supportsLangaugeName: supportsLanguageNameSpy,
64+
resolveDatabase: resolveDatabaseSpy
65+
} as unknown as CodeQLCliServer,
5866
{} as Logger,
5967
);
6068

@@ -278,9 +286,29 @@ describe('databases', () => {
278286
});
279287
});
280288

281-
it('should find likely db language folders', () => {
282-
expect(isLikelyDbLanguageFolder('db-javascript')).to.be.true;
283-
expect(isLikelyDbLanguageFolder('dbnot-a-db')).to.be.false;
289+
it('should not support the primary language', async () => {
290+
supportsLanguageNameSpy.resolves(false);
291+
292+
const result = (await (databaseManager as any).getPrimaryLanguage('hucairz'));
293+
expect(result).to.be.undefined;
294+
});
295+
296+
it('should get the primary language', async () => {
297+
supportsLanguageNameSpy.resolves(true);
298+
resolveDatabaseSpy.resolves({
299+
languages: ['python']
300+
});
301+
const result = (await (databaseManager as any).getPrimaryLanguage('hucairz'));
302+
expect(result).to.eq('python');
303+
});
304+
305+
it('should handle missing the primary language', async () => {
306+
supportsLanguageNameSpy.resolves(true);
307+
resolveDatabaseSpy.resolves({
308+
languages: []
309+
});
310+
const result = (await (databaseManager as any).getPrimaryLanguage('hucairz'));
311+
expect(result).to.eq('');
284312
});
285313

286314
function createMockDB(

extensions/ql-vscode/src/vscode-tests/no-workspace/helpers.test.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as tmp from 'tmp';
66
import * as path from 'path';
77
import * as fs from 'fs-extra';
88

9-
import { getInitialQueryContents, getPrimaryLanguage, InvocationRateLimiter } from '../../helpers';
9+
import { getInitialQueryContents, InvocationRateLimiter, isLikelyDbLanguageFolder } from '../../helpers';
1010

1111
describe('Invocation rate limiter', () => {
1212
// 1 January 2020
@@ -105,14 +105,6 @@ describe('codeql-database.yml tests', () => {
105105
dir.removeCallback();
106106
});
107107

108-
it('should get the language of a database', async () => {
109-
expect(await getPrimaryLanguage(dir.name)).to.eq('cpp');
110-
});
111-
112-
it('should get the language of a database when langauge is not known', async () => {
113-
expect(await getPrimaryLanguage('xxx')).to.eq('');
114-
});
115-
116108
it('should get initial query contents when language is known', () => {
117109
expect(getInitialQueryContents('cpp', 'hucairz')).to.eq('import cpp\n\nselect ""');
118110
});
@@ -126,6 +118,11 @@ describe('codeql-database.yml tests', () => {
126118
});
127119
});
128120

121+
it('should find likely db language folders', () => {
122+
expect(isLikelyDbLanguageFolder('db-javascript')).to.be.true;
123+
expect(isLikelyDbLanguageFolder('dbnot-a-db')).to.be.false;
124+
});
125+
129126
class MockExtensionContext implements ExtensionContext {
130127
subscriptions: { dispose(): unknown }[] = [];
131128
workspaceState: Memento = new MockMemento();

0 commit comments

Comments
 (0)