Autodetect language using "resolve queries"

Also use autodection in relevant places
- When running on multiple databases
- When running a remote query
This commit is contained in:
shati-patel
2021-07-26 12:11:19 +01:00
committed by Shati Patel
parent 72776e8254
commit 1afe6b56fa
3 changed files with 83 additions and 9 deletions

View File

@@ -8,7 +8,7 @@ import { Readable } from 'stream';
import { StringDecoder } from 'string_decoder';
import * as tk from 'tree-kill';
import { promisify } from 'util';
import { CancellationToken, Disposable } from 'vscode';
import { CancellationToken, Disposable, Uri } from 'vscode';
import { BQRSInfo, DecodedBqrsChunk } from './pure/bqrs-cli-types';
import { CliConfig } from './config';
@@ -43,6 +43,16 @@ export interface QuerySetup {
compilationCache?: string;
}
/**
* The expected output of `codeql resolve queries --format bylanguage`.
*/
export interface QueryInfoByLanguage {
// Using `unknown` as a placeholder. For now, the value is only ever an empty object.
byLanguage: Record<string, Record<string, unknown>>;
noDeclaredLanguage: Record<string, unknown>;
multipleDeclaredLanguages: Record<string, unknown>;
}
/**
* The expected output of `codeql resolve database`.
*/
@@ -71,6 +81,11 @@ export interface UpgradesInfo {
*/
export type QlpacksInfo = { [name: string]: string[] };
/**
* The expected output of `codeql resolve languages`.
*/
export type LanguagesInfo = { [name: string]: string[] };
/**
* The expected output of `codeql resolve qlref`.
*/
@@ -482,6 +497,20 @@ export class CodeQLCliServer implements Disposable {
return await this.runJsonCodeQlCliCommand<QuerySetup>(['resolve', 'library-path'], subcommandArgs, 'Resolving library paths');
}
/**
* Resolves the language for a query.
* @param queryUri The URI of the query
*/
async resolveQueryByLanguage(workspaces: string[], queryUri: Uri): Promise<QueryInfoByLanguage> {
const subcommandArgs = [
'--format', 'bylanguage',
queryUri.fsPath,
'--additional-packs',
workspaces.join(path.delimiter)
];
return JSON.parse(await this.runCodeQlCliCommand(['resolve', 'queries'], subcommandArgs, 'Resolving query by language'));
}
/**
* Finds all available QL tests in a given directory.
* @param testPath Root of directory tree to search for tests.
@@ -724,6 +753,14 @@ export class CodeQLCliServer implements Disposable {
);
}
/**
* Gets information about the available languages.
* @returns A dictionary mapping language name to the directory it comes from
*/
async resolveLanguages(): Promise<LanguagesInfo> {
return await this.runJsonCodeQlCliCommand<LanguagesInfo>(['resolve', 'languages'], [], 'Resolving languages');
}
/**
* Gets information about queries in a query suite.
* @param suite The suite to resolve.

View File

@@ -73,7 +73,7 @@ import {
import { CodeQlStatusBarHandler } from './status-bar';
import { Credentials } from './authentication';
import runRemoteQuery from './run-remote-query';
import { runRemoteQuery, findLanguage } from './run-remote-query';
/**
* extension.ts
@@ -570,7 +570,13 @@ async function activateWithInstalledDistribution(
token: CancellationToken,
uri: Uri | undefined
) => {
const quickPickItems = dbm.databaseItems.map<DatabaseQuickPickItem>(dbItem => (
const queryLanguage = await findLanguage(cliServer, uri);
const filteredDBs = dbm.databaseItems.filter(db => db.language === queryLanguage);
if (filteredDBs.length === 0) {
void helpers.showAndLogErrorMessage(`No databases found for language ${queryLanguage}`);
return;
}
const quickPickItems = filteredDBs.map<DatabaseQuickPickItem>(dbItem => (
{
databaseItem: dbItem,
label: dbItem.name,
@@ -707,7 +713,7 @@ async function activateWithInstalledDistribution(
) => {
if (isCanary()) {
const credentials = await Credentials.initialize(ctx);
await runRemoteQuery(credentials, uri || window.activeTextEditor?.document.uri);
await runRemoteQuery(cliServer, credentials, uri || window.activeTextEditor?.document.uri);
}
})
);

View File

@@ -1,20 +1,51 @@
import { Uri } from 'vscode';
import { Uri, window } from 'vscode';
import * as yaml from 'js-yaml';
import * as fs from 'fs-extra';
import { showAndLogErrorMessage, showAndLogInformationMessage } from './helpers';
import { getOnDiskWorkspaceFolders, showAndLogErrorMessage, showAndLogInformationMessage } from './helpers';
import { Credentials } from './authentication';
import * as cli from './cli';
import { logger } from './logging';
interface Config {
repositories: string[];
ref?: string;
language: string;
language?: string;
}
// Test "controller" repository and workflow.
const OWNER = 'dsp-testing';
const REPO = 'qc-controller';
export default async function runRemoteQuery(credentials: Credentials, uri?: Uri) {
/**
* Finds the language that a query targets.
* If it can't be autodetected, prompt the user to specify the language manually.
*/
export async function findLanguage(
cliServer: cli.CodeQLCliServer,
queryUri: Uri | undefined
): Promise<string> {
const uri = queryUri || window.activeTextEditor?.document.uri;
if (uri !== undefined) {
try {
const queryInfo = await cliServer.resolveQueryByLanguage(getOnDiskWorkspaceFolders(), uri);
return (Object.keys(queryInfo.byLanguage))[0];
} catch (e) {
void logger.log('Could not autodetect query language. Select language manually.');
}
}
const availableLanguages = Object.keys(await cliServer.resolveLanguages());
const language = await window.showQuickPick(
availableLanguages,
{ placeHolder: 'Select target language for your query', ignoreFocusOut: true }
) || '';
if (language === '') {
// This only happens if the user cancels the quick pick.
void showAndLogErrorMessage('Language not found. Language must be specified manually.');
}
return language;
}
export async function runRemoteQuery(cliServer: cli.CodeQLCliServer, credentials: Credentials, uri?: Uri) {
if (!uri?.fsPath.endsWith('.ql')) {
return;
}
@@ -34,7 +65,7 @@ export default async function runRemoteQuery(credentials: Credentials, uri?: Uri
const config = yaml.safeLoad(await fs.readFile(repositoriesFile, 'utf8')) as Config;
const ref = config.ref || 'main';
const language = config.language;
const language = config.language || await findLanguage(cliServer, uri);
const repositories = config.repositories;
try {