diff --git a/extensions/ql-vscode/CHANGELOG.md b/extensions/ql-vscode/CHANGELOG.md index 072da4d50..7103c61a3 100644 --- a/extensions/ql-vscode/CHANGELOG.md +++ b/extensions/ql-vscode/CHANGELOG.md @@ -4,6 +4,7 @@ - Better formatting and autoindentation when adding QLDoc comments to `.ql` and `.qll` files. - Allow for more flexibility when opening a database in the workspace. A user can now choose the actual database folder, or the nested `db-*` folder. +- Add query history menu command for viewing corresponding SARIF file. ## 1.2.0 - 19 May 2020 diff --git a/extensions/ql-vscode/package.json b/extensions/ql-vscode/package.json index a3dbadeeb..1ac130686 100644 --- a/extensions/ql-vscode/package.json +++ b/extensions/ql-vscode/package.json @@ -284,6 +284,10 @@ "command": "codeQLQueryHistory.showQueryText", "title": "Show Query Text" }, + { + "command": "codeQLQueryHistory.viewSarif", + "title": "View SARIF" + }, { "command": "codeQLQueryResults.nextPathStep", "title": "CodeQL: Show Next Step on Path" @@ -388,6 +392,11 @@ "group": "9_qlCommands", "when": "view == codeQLQueryHistory" }, + { + "command": "codeQLQueryHistory.viewSarif", + "group": "9_qlCommands", + "when": "view == codeQLQueryHistory && viewItem == interpretedResultsItem" + }, { "command": "codeQLTests.showOutputDifferences", "group": "qltest@1", @@ -484,6 +493,10 @@ "command": "codeQLQueryHistory.showQueryText", "when": "false" }, + { + "command": "codeQLQueryHistory.viewSarif", + "when": "false" + }, { "command": "codeQLQueryHistory.setLabel", "when": "false" diff --git a/extensions/ql-vscode/src/interface.ts b/extensions/ql-vscode/src/interface.ts index 17acc1cf1..239f09fe9 100644 --- a/extensions/ql-vscode/src/interface.ts +++ b/extensions/ql-vscode/src/interface.ts @@ -402,7 +402,7 @@ export class InterfaceManager extends DisposableObject { const sarif = await interpretResults( this.cliServer, metadata, - resultsPaths.resultsPath, + resultsPaths, sourceInfo ); // For performance reasons, limit the number of results we try @@ -440,7 +440,7 @@ export class InterfaceManager extends DisposableObject { ): Promise { let interpretation: Interpretation | undefined = undefined; if ( - (await query.hasInterpretedResults()) && + (await query.canHaveInterpretedResults()) && query.quickEvalPosition === undefined // never do results interpretation if quickEval ) { try { diff --git a/extensions/ql-vscode/src/query-history.ts b/extensions/ql-vscode/src/query-history.ts index 1ae0536dc..e4d917c67 100644 --- a/extensions/ql-vscode/src/query-history.ts +++ b/extensions/ql-vscode/src/query-history.ts @@ -74,7 +74,7 @@ class HistoryTreeDataProvider implements vscode.TreeDataProvider constructor(private ctx: ExtensionContext) { } - getTreeItem(element: CompletedQuery): vscode.TreeItem { + async getTreeItem(element: CompletedQuery): Promise { const it = new vscode.TreeItem(element.toString()); it.command = { @@ -83,6 +83,11 @@ class HistoryTreeDataProvider implements vscode.TreeDataProvider arguments: [element], }; + // Mark this query history item according to whether it has a + // SARIF file so that we can make context menu items conditionally + // available. + it.contextValue = await element.query.hasInterpretedResults() ? 'interpretedResultsItem' : 'rawResultsItem'; + if (!element.didRunSuccessfully) { it.iconPath = path.join(this.ctx.extensionPath, FAILED_QUERY_HISTORY_ITEM_ICON); } @@ -257,6 +262,22 @@ export class QueryHistoryManager { } } + async handleViewSarif(queryHistoryItem: CompletedQuery) { + try { + const hasInterpretedResults = await queryHistoryItem.query.canHaveInterpretedResults(); + if (hasInterpretedResults) { + const textDocument = await vscode.workspace.openTextDocument(vscode.Uri.file(queryHistoryItem.query.resultsPaths.interpretedResultsPath)); + await vscode.window.showTextDocument(textDocument, vscode.ViewColumn.One); + } + else { + const label = queryHistoryItem.getLabel(); + helpers.showAndLogInformationMessage(`Query ${label} has no interpreted results.`); + } + } catch (e) { + helpers.showAndLogErrorMessage(e.message); + } + } + async getQueryText(queryHistoryItem: CompletedQuery): Promise { if (queryHistoryItem.options.queryText) { return queryHistoryItem.options.queryText; @@ -296,6 +317,7 @@ export class QueryHistoryManager { ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.setLabel', this.handleSetLabel.bind(this))); ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.showQueryLog', this.handleShowQueryLog.bind(this))); ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.showQueryText', this.handleShowQueryText.bind(this))); + ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.viewSarif', this.handleViewSarif.bind(this))); ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.itemClicked', async (item) => { return this.handleItemClicked(item); })); diff --git a/extensions/ql-vscode/src/query-results.ts b/extensions/ql-vscode/src/query-results.ts index 3d221d324..d3993603a 100644 --- a/extensions/ql-vscode/src/query-results.ts +++ b/extensions/ql-vscode/src/query-results.ts @@ -5,7 +5,7 @@ import * as cli from './cli'; import * as sarif from 'sarif'; import * as fs from 'fs-extra'; import * as path from 'path'; -import { RawResultsSortState, SortedResultSetInfo, DatabaseInfo, QueryMetadata, InterpretedResultsSortState } from "./interface-types"; +import { RawResultsSortState, SortedResultSetInfo, DatabaseInfo, QueryMetadata, InterpretedResultsSortState, ResultsPaths } from "./interface-types"; import { QueryHistoryConfig } from "./config"; import { QueryHistoryItemOptions } from "./query-history"; @@ -54,13 +54,6 @@ export class CompletedQuery implements QueryWithResults { return helpers.getQueryName(this.query); } - /** - * Holds if this query should produce interpreted results. - */ - canInterpretedResults(): Promise { - return this.query.dbItem.hasMetadataFile(); - } - get statusString(): string { switch (this.result.resultType) { case messages.QueryResultType.CANCELLATION: @@ -130,9 +123,8 @@ export class CompletedQuery implements QueryWithResults { /** * Call cli command to interpret results. */ -export async function interpretResults(server: cli.CodeQLCliServer, metadata: QueryMetadata | undefined, resultsPath: string, sourceInfo?: cli.SourceInfo): Promise { - const interpretedResultsPath = resultsPath + ".interpreted.sarif"; - +export async function interpretResults(server: cli.CodeQLCliServer, metadata: QueryMetadata | undefined, resultsPaths: ResultsPaths, sourceInfo?: cli.SourceInfo): Promise { + const { resultsPath, interpretedResultsPath } = resultsPaths; if (await fs.pathExists(interpretedResultsPath)) { return JSON.parse(await fs.readFile(interpretedResultsPath, 'utf8')); } diff --git a/extensions/ql-vscode/src/run-queries.ts b/extensions/ql-vscode/src/run-queries.ts index 9b456e07b..544f5a6bf 100644 --- a/extensions/ql-vscode/src/run-queries.ts +++ b/extensions/ql-vscode/src/run-queries.ts @@ -157,15 +157,22 @@ export class QueryInfo { } /** - * Holds if this query should produce interpreted results. + * Holds if this query can in principle produce interpreted results. */ - async hasInterpretedResults(): Promise { + async canHaveInterpretedResults(): Promise { const hasMetadataFile = await this.dbItem.hasMetadataFile(); if (!hasMetadataFile) { logger.log("Cannot produce interpreted results since the database does not have a .dbinfo or codeql-database.yml file."); } return hasMetadataFile; } + + /** + * Holds if this query actually has produced interpreted results. + */ + async hasInterpretedResults(): Promise { + return fs.pathExists(this.resultsPaths.interpretedResultsPath); + } } export interface QueryWithResults {