Add command to view the DIL of a query
This commit is contained in:
Родитель
8e817ee01a
Коммит
9300c07d42
|
@ -12,6 +12,7 @@
|
|||
- Fix proper escaping of backslashes in SARIF message strings.
|
||||
- Allow setting `codeQL.runningQueries.numberOfThreads` and `codeQL.runningTests.numberOfThreads` to 0, (which is interpreted as 'use one thread per core on the machine').
|
||||
- Clear the problems view of all CodeQL query results when a database is removed.
|
||||
- Add a `View DIL` command on query history items. This opens a text editor containing the Datalog Intermediary Language representation of the compiled query.
|
||||
|
||||
## 1.3.3 - 16 September 2020
|
||||
|
||||
|
|
|
@ -329,6 +329,10 @@
|
|||
"command": "codeQLQueryHistory.viewSarif",
|
||||
"title": "View SARIF"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewDil",
|
||||
"title": "View DIL"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.setLabel",
|
||||
"title": "Set Label"
|
||||
|
@ -484,6 +488,11 @@
|
|||
"group": "9_qlCommands",
|
||||
"when": "view == codeQLQueryHistory && viewItem == interpretedResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewDil",
|
||||
"group": "9_qlCommands",
|
||||
"when": "view == codeQLQueryHistory"
|
||||
},
|
||||
{
|
||||
"command": "codeQLTests.showOutputDifferences",
|
||||
"group": "qltest@1",
|
||||
|
@ -600,6 +609,10 @@
|
|||
"command": "codeQLQueryHistory.viewSarif",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewDil",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.setLabel",
|
||||
"when": "false"
|
||||
|
|
|
@ -650,6 +650,14 @@ export class CodeQLCliServer implements Disposable {
|
|||
'Resolving queries',
|
||||
);
|
||||
}
|
||||
|
||||
async generateDil(qloFile: string, outFile: string): Promise<void> {
|
||||
await this.runCodeQlCliCommand(
|
||||
['query', 'decompile'],
|
||||
['--kind', 'dil', '-o', outFile, qloFile],
|
||||
'Generating DIL',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -353,6 +353,7 @@ async function activateWithInstalledDistribution(
|
|||
|
||||
const qhm = new QueryHistoryManager(
|
||||
ctx,
|
||||
qs,
|
||||
queryHistoryConfigurationListener,
|
||||
showResults,
|
||||
async (from: CompletedQuery, to: CompletedQuery) =>
|
||||
|
|
|
@ -7,6 +7,7 @@ import { QueryWithResults } from './run-queries';
|
|||
import * as helpers from './helpers';
|
||||
import { logger } from './logging';
|
||||
import { URLSearchParams } from 'url';
|
||||
import { QueryServerClient } from './queryserver-client';
|
||||
|
||||
/**
|
||||
* query-history.ts
|
||||
|
@ -180,6 +181,7 @@ export class QueryHistoryManager {
|
|||
|
||||
constructor(
|
||||
ctx: ExtensionContext,
|
||||
private qs: QueryServerClient,
|
||||
private queryHistoryConfigListener: QueryHistoryConfig,
|
||||
private selectedCallback: (item: CompletedQuery) => Promise<void>,
|
||||
private doCompareCallback: (
|
||||
|
@ -250,6 +252,12 @@ export class QueryHistoryManager {
|
|||
this.handleViewSarif.bind(this)
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
helpers.commandRunner(
|
||||
'codeQLQueryHistory.viewDil',
|
||||
this.handleViewDil.bind(this)
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
helpers.commandRunner(
|
||||
'codeQLQueryHistory.itemClicked',
|
||||
|
@ -459,6 +467,19 @@ export class QueryHistoryManager {
|
|||
}
|
||||
}
|
||||
|
||||
async handleViewDil(
|
||||
singleItem: CompletedQuery,
|
||||
multiSelect: CompletedQuery[],
|
||||
) {
|
||||
if (!this.assertSingleQuery(multiSelect)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.tryOpenExternalFile(
|
||||
await singleItem.query.ensureDilPath(this.qs)
|
||||
);
|
||||
}
|
||||
|
||||
async getQueryText(queryHistoryItem: CompletedQuery): Promise<string> {
|
||||
if (queryHistoryItem.options.queryText) {
|
||||
return queryHistoryItem.options.queryText;
|
||||
|
@ -511,7 +532,9 @@ export class QueryHistoryManager {
|
|||
private async tryOpenExternalFile(fileLocation: string) {
|
||||
const uri = vscode.Uri.file(fileLocation);
|
||||
try {
|
||||
await vscode.window.showTextDocument(uri);
|
||||
await vscode.window.showTextDocument(uri, {
|
||||
preview: false
|
||||
});
|
||||
} catch (e) {
|
||||
if (
|
||||
e.message.includes(
|
||||
|
|
|
@ -51,6 +51,7 @@ export class QueryInfo {
|
|||
private static nextQueryId = 0;
|
||||
|
||||
readonly compiledQueryPath: string;
|
||||
readonly dilPath: string;
|
||||
readonly resultsPaths: ResultsPaths;
|
||||
readonly dataset: Uri; // guarantee the existence of a well-defined dataset dir at this point
|
||||
readonly queryID: number;
|
||||
|
@ -65,9 +66,10 @@ export class QueryInfo {
|
|||
) {
|
||||
this.queryID = QueryInfo.nextQueryId++;
|
||||
this.compiledQueryPath = path.join(tmpDir.name, `compiledQuery${this.queryID}.qlo`);
|
||||
this.dilPath = path.join(tmpDir.name, `results${this.queryID}.dil`);
|
||||
this.resultsPaths = {
|
||||
resultsPath: path.join(tmpDir.name, `results${this.queryID}.bqrs`),
|
||||
interpretedResultsPath: path.join(tmpDir.name, `interpretedResults${this.queryID}.sarif`),
|
||||
interpretedResultsPath: path.join(tmpDir.name, `interpretedResults${this.queryID}.sarif`)
|
||||
};
|
||||
if (dbItem.contents === undefined) {
|
||||
throw new Error('Can\'t run query on invalid database.');
|
||||
|
@ -169,6 +171,29 @@ export class QueryInfo {
|
|||
async hasInterpretedResults(): Promise<boolean> {
|
||||
return fs.pathExists(this.resultsPaths.interpretedResultsPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this query already has DIL produced
|
||||
*/
|
||||
async hasDil(): Promise<boolean> {
|
||||
return fs.pathExists(this.dilPath);
|
||||
}
|
||||
|
||||
async ensureDilPath(qs: qsClient.QueryServerClient): Promise<string> {
|
||||
if (await this.hasDil()) {
|
||||
return this.dilPath;
|
||||
}
|
||||
|
||||
if (!(await fs.pathExists(this.compiledQueryPath))) {
|
||||
throw new Error(
|
||||
`Cannot create DIL because compiled query is missing. ${this.compiledQueryPath}`
|
||||
);
|
||||
}
|
||||
|
||||
await qs.cliServer.generateDil(this.compiledQueryPath, this.dilPath);
|
||||
return this.dilPath;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface QueryWithResults {
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
import * as chai from 'chai';
|
||||
import * as path from 'path';
|
||||
import 'mocha';
|
||||
import 'sinon-chai';
|
||||
import * as sinon from 'sinon';
|
||||
import * as chaiAsPromised from 'chai-as-promised';
|
||||
|
||||
import { QueryInfo } from '../../run-queries';
|
||||
import { QlProgram, Severity, compileQuery } from '../../messages';
|
||||
import { DatabaseItem } from '../../databases';
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('run-queries', () => {
|
||||
it('should create a QueryInfo', () => {
|
||||
const info = createMockQueryInfo();
|
||||
|
||||
const queryID = info.queryID;
|
||||
expect(path.basename(info.compiledQueryPath)).to.eq(`compiledQuery${queryID}.qlo`);
|
||||
expect(path.basename(info.dilPath)).to.eq(`results${queryID}.dil`);
|
||||
expect(path.basename(info.resultsPaths.resultsPath)).to.eq(`results${queryID}.bqrs`);
|
||||
expect(path.basename(info.resultsPaths.interpretedResultsPath)).to.eq(`interpretedResults${queryID}.sarif`);
|
||||
expect(info.dataset).to.eq('file:///abc');
|
||||
});
|
||||
|
||||
describe('compile', () => {
|
||||
it('should compile', async () => {
|
||||
const info = createMockQueryInfo();
|
||||
const qs = createMockQueryServerClient();
|
||||
const mockProgress = 'progress-monitor';
|
||||
const mockCancel = 'cancel-token';
|
||||
|
||||
const results = await info.compile(
|
||||
qs as any,
|
||||
mockProgress as any,
|
||||
mockCancel as any
|
||||
);
|
||||
|
||||
expect(results).to.deep.eq([
|
||||
{ message: 'err', severity: Severity.ERROR }
|
||||
]);
|
||||
|
||||
expect(qs.sendRequest).to.have.been.calledOnceWith(
|
||||
compileQuery,
|
||||
{
|
||||
compilationOptions: {
|
||||
computeNoLocationUrls: true,
|
||||
failOnWarnings: false,
|
||||
fastCompilation: false,
|
||||
includeDilInQlo: true,
|
||||
localChecking: false,
|
||||
noComputeGetUrl: false,
|
||||
noComputeToString: false,
|
||||
},
|
||||
extraOptions: {
|
||||
timeoutSecs: 5
|
||||
},
|
||||
queryToCheck: 'my-program',
|
||||
resultPath: info.compiledQueryPath,
|
||||
target: { query: {} }
|
||||
},
|
||||
mockCancel,
|
||||
mockProgress
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function createMockQueryInfo() {
|
||||
return new QueryInfo(
|
||||
'my-program' as unknown as QlProgram,
|
||||
{
|
||||
contents: {
|
||||
datasetUri: 'file:///abc'
|
||||
}
|
||||
} as unknown as DatabaseItem,
|
||||
'my-scheme' // queryDbscheme
|
||||
);
|
||||
}
|
||||
|
||||
function createMockQueryServerClient() {
|
||||
return {
|
||||
config: {
|
||||
timeoutSecs: 5
|
||||
},
|
||||
sendRequest: sinon.stub().returns(new Promise(resolve => {
|
||||
resolve({
|
||||
messages: [
|
||||
{ message: 'err', severity: Severity.ERROR },
|
||||
{ message: 'warn', severity: Severity.WARNING },
|
||||
]
|
||||
});
|
||||
})),
|
||||
logger: {
|
||||
log: sinon.spy()
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
Загрузка…
Ссылка в новой задаче