Merge pull request #407 from jcreedcmu/jcreed/view-sarif

Allow viewing SARIF from query history view
This commit is contained in:
jcreedcmu
2020-05-28 08:08:56 -04:00
committed by GitHub
6 changed files with 51 additions and 16 deletions

View File

@@ -4,6 +4,7 @@
- Better formatting and autoindentation when adding QLDoc comments to `.ql` and `.qll` files. - 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. - 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 ## 1.2.0 - 19 May 2020

View File

@@ -284,6 +284,10 @@
"command": "codeQLQueryHistory.showQueryText", "command": "codeQLQueryHistory.showQueryText",
"title": "Show Query Text" "title": "Show Query Text"
}, },
{
"command": "codeQLQueryHistory.viewSarif",
"title": "View SARIF"
},
{ {
"command": "codeQLQueryResults.nextPathStep", "command": "codeQLQueryResults.nextPathStep",
"title": "CodeQL: Show Next Step on Path" "title": "CodeQL: Show Next Step on Path"
@@ -388,6 +392,11 @@
"group": "9_qlCommands", "group": "9_qlCommands",
"when": "view == codeQLQueryHistory" "when": "view == codeQLQueryHistory"
}, },
{
"command": "codeQLQueryHistory.viewSarif",
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory && viewItem == interpretedResultsItem"
},
{ {
"command": "codeQLTests.showOutputDifferences", "command": "codeQLTests.showOutputDifferences",
"group": "qltest@1", "group": "qltest@1",
@@ -484,6 +493,10 @@
"command": "codeQLQueryHistory.showQueryText", "command": "codeQLQueryHistory.showQueryText",
"when": "false" "when": "false"
}, },
{
"command": "codeQLQueryHistory.viewSarif",
"when": "false"
},
{ {
"command": "codeQLQueryHistory.setLabel", "command": "codeQLQueryHistory.setLabel",
"when": "false" "when": "false"

View File

@@ -402,7 +402,7 @@ export class InterfaceManager extends DisposableObject {
const sarif = await interpretResults( const sarif = await interpretResults(
this.cliServer, this.cliServer,
metadata, metadata,
resultsPaths.resultsPath, resultsPaths,
sourceInfo sourceInfo
); );
// For performance reasons, limit the number of results we try // For performance reasons, limit the number of results we try
@@ -440,7 +440,7 @@ export class InterfaceManager extends DisposableObject {
): Promise<Interpretation | undefined> { ): Promise<Interpretation | undefined> {
let interpretation: Interpretation | undefined = undefined; let interpretation: Interpretation | undefined = undefined;
if ( if (
(await query.hasInterpretedResults()) && (await query.canHaveInterpretedResults()) &&
query.quickEvalPosition === undefined // never do results interpretation if quickEval query.quickEvalPosition === undefined // never do results interpretation if quickEval
) { ) {
try { try {

View File

@@ -74,7 +74,7 @@ class HistoryTreeDataProvider implements vscode.TreeDataProvider<CompletedQuery>
constructor(private ctx: ExtensionContext) { constructor(private ctx: ExtensionContext) {
} }
getTreeItem(element: CompletedQuery): vscode.TreeItem { async getTreeItem(element: CompletedQuery): Promise<vscode.TreeItem> {
const it = new vscode.TreeItem(element.toString()); const it = new vscode.TreeItem(element.toString());
it.command = { it.command = {
@@ -83,6 +83,11 @@ class HistoryTreeDataProvider implements vscode.TreeDataProvider<CompletedQuery>
arguments: [element], 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) { if (!element.didRunSuccessfully) {
it.iconPath = path.join(this.ctx.extensionPath, FAILED_QUERY_HISTORY_ITEM_ICON); 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<string> { async getQueryText(queryHistoryItem: CompletedQuery): Promise<string> {
if (queryHistoryItem.options.queryText) { if (queryHistoryItem.options.queryText) {
return 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.setLabel', this.handleSetLabel.bind(this)));
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.showQueryLog', this.handleShowQueryLog.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.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) => { ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.itemClicked', async (item) => {
return this.handleItemClicked(item); return this.handleItemClicked(item);
})); }));

View File

@@ -5,7 +5,7 @@ import * as cli from './cli';
import * as sarif from 'sarif'; import * as sarif from 'sarif';
import * as fs from 'fs-extra'; import * as fs from 'fs-extra';
import * as path from 'path'; 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 { QueryHistoryConfig } from "./config";
import { QueryHistoryItemOptions } from "./query-history"; import { QueryHistoryItemOptions } from "./query-history";
@@ -54,13 +54,6 @@ export class CompletedQuery implements QueryWithResults {
return helpers.getQueryName(this.query); return helpers.getQueryName(this.query);
} }
/**
* Holds if this query should produce interpreted results.
*/
canInterpretedResults(): Promise<boolean> {
return this.query.dbItem.hasMetadataFile();
}
get statusString(): string { get statusString(): string {
switch (this.result.resultType) { switch (this.result.resultType) {
case messages.QueryResultType.CANCELLATION: case messages.QueryResultType.CANCELLATION:
@@ -130,9 +123,8 @@ export class CompletedQuery implements QueryWithResults {
/** /**
* Call cli command to interpret results. * Call cli command to interpret results.
*/ */
export async function interpretResults(server: cli.CodeQLCliServer, metadata: QueryMetadata | undefined, resultsPath: string, sourceInfo?: cli.SourceInfo): Promise<sarif.Log> { export async function interpretResults(server: cli.CodeQLCliServer, metadata: QueryMetadata | undefined, resultsPaths: ResultsPaths, sourceInfo?: cli.SourceInfo): Promise<sarif.Log> {
const interpretedResultsPath = resultsPath + ".interpreted.sarif"; const { resultsPath, interpretedResultsPath } = resultsPaths;
if (await fs.pathExists(interpretedResultsPath)) { if (await fs.pathExists(interpretedResultsPath)) {
return JSON.parse(await fs.readFile(interpretedResultsPath, 'utf8')); return JSON.parse(await fs.readFile(interpretedResultsPath, 'utf8'));
} }

View File

@@ -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<boolean> { async canHaveInterpretedResults(): Promise<boolean> {
const hasMetadataFile = await this.dbItem.hasMetadataFile(); const hasMetadataFile = await this.dbItem.hasMetadataFile();
if (!hasMetadataFile) { if (!hasMetadataFile) {
logger.log("Cannot produce interpreted results since the database does not have a .dbinfo or codeql-database.yml file."); logger.log("Cannot produce interpreted results since the database does not have a .dbinfo or codeql-database.yml file.");
} }
return hasMetadataFile; return hasMetadataFile;
} }
/**
* Holds if this query actually has produced interpreted results.
*/
async hasInterpretedResults(): Promise<boolean> {
return fs.pathExists(this.resultsPaths.interpretedResultsPath);
}
} }
export interface QueryWithResults { export interface QueryWithResults {