Skip to content

Commit 67e8c86

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 43ef44f commit 67e8c86

6 files changed

Lines changed: 94 additions & 39 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: 22 additions & 9 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
ProgressCallback,
1413
withProgress
@@ -51,7 +50,7 @@ export interface DatabaseOptions {
5150
export interface FullDatabaseOptions extends DatabaseOptions {
5251
ignoreSourceArchive: boolean;
5352
dateAdded: number | undefined;
54-
language: string;
53+
language: string | undefined;
5554
}
5655

5756
interface PersistedDatabaseItem {
@@ -508,7 +507,8 @@ export class DatabaseManager extends DisposableObject {
508507
constructor(
509508
private readonly ctx: ExtensionContext,
510509
private readonly qs: QueryServerClient,
511-
public readonly logger: Logger
510+
private readonly cli: cli.CodeQLCliServer,
511+
public logger: Logger
512512
) {
513513
super();
514514

@@ -528,7 +528,7 @@ export class DatabaseManager extends DisposableObject {
528528
// displayName is only set if a user explicitly renames a database
529529
displayName: undefined,
530530
dateAdded: Date.now(),
531-
language: await getPrimaryLanguage(uri.fsPath)
531+
language: await this.getPrimaryLanguage(uri.fsPath)
532532
};
533533
const databaseItem = new DatabaseItemImpl(uri, contents, fullOptions, (event) => {
534534
this._onDidChangeDatabaseItem.fire(event);
@@ -588,7 +588,7 @@ export class DatabaseManager extends DisposableObject {
588588
let displayName: string | undefined = undefined;
589589
let ignoreSourceArchive = false;
590590
let dateAdded = undefined;
591-
let language = '';
591+
let language = undefined;
592592
if (state.options) {
593593
if (typeof state.options.displayName === 'string') {
594594
displayName = state.options.displayName;
@@ -599,9 +599,13 @@ export class DatabaseManager extends DisposableObject {
599599
if (typeof state.options.dateAdded === 'number') {
600600
dateAdded = state.options.dateAdded;
601601
}
602-
if (state.options.language) {
603-
language = state.options.language;
604-
}
602+
language = state.options.language;
603+
}
604+
605+
const dbBaseUri = vscode.Uri.parse(state.uri, true);
606+
if (language === undefined) {
607+
// we haven't been successful yet at getting the language. try again
608+
language = await this.getPrimaryLanguage(dbBaseUri.fsPath);
605609
}
606610

607611
const fullOptions: FullDatabaseOptions = {
@@ -610,7 +614,7 @@ export class DatabaseManager extends DisposableObject {
610614
dateAdded,
611615
language
612616
};
613-
const item = new DatabaseItemImpl(vscode.Uri.parse(state.uri, true), undefined, fullOptions,
617+
const item = new DatabaseItemImpl(dbBaseUri, undefined, fullOptions,
614618
(event) => {
615619
this._onDidChangeDatabaseItem.fire(event);
616620
});
@@ -825,6 +829,15 @@ export class DatabaseManager extends DisposableObject {
825829
}
826830
return false;
827831
}
832+
833+
private async getPrimaryLanguage(dbPath: string) {
834+
if (!(await this.cli.supportsLangaugeName())) {
835+
// return undefined so that we continually recalculate until the cli version is bumped
836+
return undefined;
837+
}
838+
const dbInfo = await this.cli.resolveDatabase(dbPath);
839+
return dbInfo.languages?.[0] || '';
840+
}
828841
}
829842

830843
/**

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, qs, logger);
342+
const dbm = new DatabaseManager(ctx, qs, 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: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ import {
1515
FullDatabaseOptions
1616
} from '../../databases';
1717
import { Logger } from '../../logging';
18-
import { encodeArchiveBasePath, encodeSourceArchiveUri } from '../../archive-filesystem-provider';
1918
import { QueryServerClient } from '../../queryserver-client';
2019
import { registerDatabases } from '../../pure/messages';
21-
import { isLikelyDbLanguageFolder, ProgressCallback } from '../../helpers';
20+
import { ProgressCallback } from '../../helpers';
21+
import { CodeQLCliServer } from '../../cli';
22+
import { encodeArchiveBasePath, encodeSourceArchiveUri } from '../../archive-filesystem-provider';
2223

2324
describe('databases', () => {
2425

@@ -34,6 +35,8 @@ describe('databases', () => {
3435
let dbChangedHandler: sinon.SinonSpy;
3536
let sendRequestSpy: sinon.SinonSpy;
3637
let supportsDatabaseRegistrationSpy: sinon.SinonStub;
38+
let supportsLanguageNameSpy: sinon.SinonStub;
39+
let resolveDatabaseSpy: sinon.SinonStub;
3740

3841
let sandbox: sinon.SinonSandbox;
3942
let dir: tmp.DirResult;
@@ -51,6 +54,8 @@ describe('databases', () => {
5154
dbChangedHandler = sandbox.spy();
5255
supportsDatabaseRegistrationSpy = sandbox.stub();
5356
supportsDatabaseRegistrationSpy.resolves(true);
57+
supportsLanguageNameSpy = sandbox.stub();
58+
resolveDatabaseSpy = sandbox.stub();
5459
databaseManager = new DatabaseManager(
5560
{
5661
workspaceState: {
@@ -65,6 +70,10 @@ describe('databases', () => {
6570
sendRequest: sendRequestSpy,
6671
supportsDatabaseRegistration: supportsDatabaseRegistrationSpy
6772
} as unknown as QueryServerClient,
73+
{
74+
supportsLangaugeName: supportsLanguageNameSpy,
75+
resolveDatabase: resolveDatabaseSpy
76+
} as unknown as CodeQLCliServer,
6877
{} as Logger,
6978
);
7079

@@ -377,9 +386,29 @@ describe('databases', () => {
377386
});
378387
});
379388

380-
it('should find likely db language folders', () => {
381-
expect(isLikelyDbLanguageFolder('db-javascript')).to.be.true;
382-
expect(isLikelyDbLanguageFolder('dbnot-a-db')).to.be.false;
389+
it('should not support the primary language', async () => {
390+
supportsLanguageNameSpy.resolves(false);
391+
392+
const result = (await (databaseManager as any).getPrimaryLanguage('hucairz'));
393+
expect(result).to.be.undefined;
394+
});
395+
396+
it('should get the primary language', async () => {
397+
supportsLanguageNameSpy.resolves(true);
398+
resolveDatabaseSpy.resolves({
399+
languages: ['python']
400+
});
401+
const result = (await (databaseManager as any).getPrimaryLanguage('hucairz'));
402+
expect(result).to.eq('python');
403+
});
404+
405+
it('should handle missing the primary language', async () => {
406+
supportsLanguageNameSpy.resolves(true);
407+
resolveDatabaseSpy.resolves({
408+
languages: []
409+
});
410+
const result = (await (databaseManager as any).getPrimaryLanguage('hucairz'));
411+
expect(result).to.eq('');
383412
});
384413

385414
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)