Skip to content

Commit 1afe6b5

Browse files
committed
Autodetect language using "resolve queries"
Also use autodection in relevant places - When running on multiple databases - When running a remote query
1 parent 72776e8 commit 1afe6b5

File tree

3 files changed

+83
-9
lines changed

3 files changed

+83
-9
lines changed

extensions/ql-vscode/src/cli.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Readable } from 'stream';
88
import { StringDecoder } from 'string_decoder';
99
import * as tk from 'tree-kill';
1010
import { promisify } from 'util';
11-
import { CancellationToken, Disposable } from 'vscode';
11+
import { CancellationToken, Disposable, Uri } from 'vscode';
1212

1313
import { BQRSInfo, DecodedBqrsChunk } from './pure/bqrs-cli-types';
1414
import { CliConfig } from './config';
@@ -43,6 +43,16 @@ export interface QuerySetup {
4343
compilationCache?: string;
4444
}
4545

46+
/**
47+
* The expected output of `codeql resolve queries --format bylanguage`.
48+
*/
49+
export interface QueryInfoByLanguage {
50+
// Using `unknown` as a placeholder. For now, the value is only ever an empty object.
51+
byLanguage: Record<string, Record<string, unknown>>;
52+
noDeclaredLanguage: Record<string, unknown>;
53+
multipleDeclaredLanguages: Record<string, unknown>;
54+
}
55+
4656
/**
4757
* The expected output of `codeql resolve database`.
4858
*/
@@ -71,6 +81,11 @@ export interface UpgradesInfo {
7181
*/
7282
export type QlpacksInfo = { [name: string]: string[] };
7383

84+
/**
85+
* The expected output of `codeql resolve languages`.
86+
*/
87+
export type LanguagesInfo = { [name: string]: string[] };
88+
7489
/**
7590
* The expected output of `codeql resolve qlref`.
7691
*/
@@ -482,6 +497,20 @@ export class CodeQLCliServer implements Disposable {
482497
return await this.runJsonCodeQlCliCommand<QuerySetup>(['resolve', 'library-path'], subcommandArgs, 'Resolving library paths');
483498
}
484499

500+
/**
501+
* Resolves the language for a query.
502+
* @param queryUri The URI of the query
503+
*/
504+
async resolveQueryByLanguage(workspaces: string[], queryUri: Uri): Promise<QueryInfoByLanguage> {
505+
const subcommandArgs = [
506+
'--format', 'bylanguage',
507+
queryUri.fsPath,
508+
'--additional-packs',
509+
workspaces.join(path.delimiter)
510+
];
511+
return JSON.parse(await this.runCodeQlCliCommand(['resolve', 'queries'], subcommandArgs, 'Resolving query by language'));
512+
}
513+
485514
/**
486515
* Finds all available QL tests in a given directory.
487516
* @param testPath Root of directory tree to search for tests.
@@ -724,6 +753,14 @@ export class CodeQLCliServer implements Disposable {
724753
);
725754
}
726755

756+
/**
757+
* Gets information about the available languages.
758+
* @returns A dictionary mapping language name to the directory it comes from
759+
*/
760+
async resolveLanguages(): Promise<LanguagesInfo> {
761+
return await this.runJsonCodeQlCliCommand<LanguagesInfo>(['resolve', 'languages'], [], 'Resolving languages');
762+
}
763+
727764
/**
728765
* Gets information about queries in a query suite.
729766
* @param suite The suite to resolve.

extensions/ql-vscode/src/extension.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ import {
7373
import { CodeQlStatusBarHandler } from './status-bar';
7474

7575
import { Credentials } from './authentication';
76-
import runRemoteQuery from './run-remote-query';
76+
import { runRemoteQuery, findLanguage } from './run-remote-query';
7777

7878
/**
7979
* extension.ts
@@ -570,7 +570,13 @@ async function activateWithInstalledDistribution(
570570
token: CancellationToken,
571571
uri: Uri | undefined
572572
) => {
573-
const quickPickItems = dbm.databaseItems.map<DatabaseQuickPickItem>(dbItem => (
573+
const queryLanguage = await findLanguage(cliServer, uri);
574+
const filteredDBs = dbm.databaseItems.filter(db => db.language === queryLanguage);
575+
if (filteredDBs.length === 0) {
576+
void helpers.showAndLogErrorMessage(`No databases found for language ${queryLanguage}`);
577+
return;
578+
}
579+
const quickPickItems = filteredDBs.map<DatabaseQuickPickItem>(dbItem => (
574580
{
575581
databaseItem: dbItem,
576582
label: dbItem.name,
@@ -707,7 +713,7 @@ async function activateWithInstalledDistribution(
707713
) => {
708714
if (isCanary()) {
709715
const credentials = await Credentials.initialize(ctx);
710-
await runRemoteQuery(credentials, uri || window.activeTextEditor?.document.uri);
716+
await runRemoteQuery(cliServer, credentials, uri || window.activeTextEditor?.document.uri);
711717
}
712718
})
713719
);

extensions/ql-vscode/src/run-remote-query.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,51 @@
1-
import { Uri } from 'vscode';
1+
import { Uri, window } from 'vscode';
22
import * as yaml from 'js-yaml';
33
import * as fs from 'fs-extra';
4-
import { showAndLogErrorMessage, showAndLogInformationMessage } from './helpers';
4+
import { getOnDiskWorkspaceFolders, showAndLogErrorMessage, showAndLogInformationMessage } from './helpers';
55
import { Credentials } from './authentication';
6+
import * as cli from './cli';
7+
import { logger } from './logging';
68

79
interface Config {
810
repositories: string[];
911
ref?: string;
10-
language: string;
12+
language?: string;
1113
}
1214

1315
// Test "controller" repository and workflow.
1416
const OWNER = 'dsp-testing';
1517
const REPO = 'qc-controller';
1618

17-
export default async function runRemoteQuery(credentials: Credentials, uri?: Uri) {
19+
/**
20+
* Finds the language that a query targets.
21+
* If it can't be autodetected, prompt the user to specify the language manually.
22+
*/
23+
export async function findLanguage(
24+
cliServer: cli.CodeQLCliServer,
25+
queryUri: Uri | undefined
26+
): Promise<string> {
27+
const uri = queryUri || window.activeTextEditor?.document.uri;
28+
if (uri !== undefined) {
29+
try {
30+
const queryInfo = await cliServer.resolveQueryByLanguage(getOnDiskWorkspaceFolders(), uri);
31+
return (Object.keys(queryInfo.byLanguage))[0];
32+
} catch (e) {
33+
void logger.log('Could not autodetect query language. Select language manually.');
34+
}
35+
}
36+
const availableLanguages = Object.keys(await cliServer.resolveLanguages());
37+
const language = await window.showQuickPick(
38+
availableLanguages,
39+
{ placeHolder: 'Select target language for your query', ignoreFocusOut: true }
40+
) || '';
41+
if (language === '') {
42+
// This only happens if the user cancels the quick pick.
43+
void showAndLogErrorMessage('Language not found. Language must be specified manually.');
44+
}
45+
return language;
46+
}
47+
48+
export async function runRemoteQuery(cliServer: cli.CodeQLCliServer, credentials: Credentials, uri?: Uri) {
1849
if (!uri?.fsPath.endsWith('.ql')) {
1950
return;
2051
}
@@ -34,7 +65,7 @@ export default async function runRemoteQuery(credentials: Credentials, uri?: Uri
3465
const config = yaml.safeLoad(await fs.readFile(repositoriesFile, 'utf8')) as Config;
3566

3667
const ref = config.ref || 'main';
37-
const language = config.language;
68+
const language = config.language || await findLanguage(cliServer, uri);
3869
const repositories = config.repositories;
3970

4071
try {

0 commit comments

Comments
 (0)