Skip to content

Commit a36b810

Browse files
committed
Refactor query results and query history
1 parent 6fee8b3 commit a36b810

File tree

6 files changed

+240
-219
lines changed

6 files changed

+240
-219
lines changed

extensions/ql-vscode/src/extension.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import * as helpers from './helpers';
1212
import { spawnIdeServer } from './ide-server';
1313
import { InterfaceManager, WebviewReveal } from './interface';
1414
import { ideServerLogger, logger, queryServerLogger } from './logging';
15-
import { compileAndRunQueryAgainstDatabase, EvaluationInfo, tmpDirDisposal, UserCancellationException } from './queries';
15+
import { compileAndRunQueryAgainstDatabase, tmpDirDisposal, UserCancellationException } from './queries';
16+
import { CompletedQuery } from './query-results';
1617
import { QueryHistoryManager } from './query-history';
1718
import * as qsClient from './queryserver-client';
1819
import { CodeQLCliServer } from './cli';
@@ -254,14 +255,14 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu
254255
const qhm = new QueryHistoryManager(
255256
ctx,
256257
queryHistoryConfigurationListener,
257-
async item => showResultsForInfo(item.info, WebviewReveal.Forced)
258+
async item => showResultsForCompletedQuery(item, WebviewReveal.Forced)
258259
);
259260
const intm = new InterfaceManager(ctx, dbm, cliServer, queryServerLogger);
260261
ctx.subscriptions.push(intm);
261262
archiveFilesystemProvider.activate(ctx);
262263

263-
async function showResultsForInfo(info: EvaluationInfo, forceReveal: WebviewReveal): Promise<void> {
264-
await intm.showResults(info, forceReveal, false);
264+
async function showResultsForCompletedQuery(query: CompletedQuery, forceReveal: WebviewReveal): Promise<void> {
265+
await intm.showResults(query, forceReveal, false);
265266
}
266267

267268
async function compileAndRunQuery(quickEval: boolean, selectedQuery: Uri | undefined) {
@@ -272,8 +273,8 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu
272273
throw new Error('Can\'t run query without a selected database');
273274
}
274275
const info = await compileAndRunQueryAgainstDatabase(cliServer, qs, dbItem, quickEval, selectedQuery);
275-
await showResultsForInfo(info, WebviewReveal.NotForced);
276-
qhm.push(info);
276+
const item = qhm.addQuery(info);
277+
await showResultsForCompletedQuery(item, WebviewReveal.NotForced);
277278
}
278279
catch (e) {
279280
if (e instanceof UserCancellationException) {

extensions/ql-vscode/src/helpers.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as path from 'path';
22
import { CancellationToken, ExtensionContext, ProgressOptions, window as Window, workspace } from 'vscode';
33
import { logger } from './logging';
4-
import { EvaluationInfo } from './queries';
4+
import { QueryInfo } from './queries';
55

66
export interface ProgressUpdate {
77
/**
@@ -121,17 +121,17 @@ export function getOnDiskWorkspaceFolders() {
121121
* Gets a human-readable name for an evaluated query.
122122
* Uses metadata if it exists, and defaults to the query file name.
123123
*/
124-
export function getQueryName(info: EvaluationInfo) {
124+
export function getQueryName(query: QueryInfo) {
125125
// Queries run through quick evaluation are not usually the entire query file.
126126
// Label them differently and include the line numbers.
127-
if (info.query.quickEvalPosition !== undefined) {
128-
const { line, endLine, fileName } = info.query.quickEvalPosition;
127+
if (query.quickEvalPosition !== undefined) {
128+
const { line, endLine, fileName } = query.quickEvalPosition;
129129
const lineInfo = line === endLine ? `${line}` : `${line}-${endLine}`;
130130
return `Quick evaluation of ${path.basename(fileName)}:${lineInfo}`;
131-
} else if (info.query.metadata && info.query.metadata.name) {
132-
return info.query.metadata.name;
131+
} else if (query.metadata && query.metadata.name) {
132+
return query.metadata.name;
133133
} else {
134-
return path.basename(info.query.program.queryPath);
134+
return path.basename(query.program.queryPath);
135135
}
136136
}
137137

extensions/ql-vscode/src/interface.ts

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@ import * as crypto from 'crypto';
22
import * as path from 'path';
33
import * as cli from './cli';
44
import * as Sarif from 'sarif';
5-
import { parseSarifLocation, parseSarifPlainTextMessage } from './sarif-utils';
5+
import { parseSarifLocation, parseSarifPlainTextMessage } from './sarif-utils';
66
import { FivePartLocation, LocationValue, ResolvableLocationValue, WholeFileLocation, tryGetResolvableLocation, LocationStyle } from 'semmle-bqrs';
77
import { DisposableObject } from 'semmle-vscode-utils';
88
import * as vscode from 'vscode';
99
import { Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, languages, Location, Range, Uri, window as Window, workspace } from 'vscode';
1010
import { CodeQLCliServer } from './cli';
1111
import { DatabaseItem, DatabaseManager } from './databases';
12-
import * as helpers from './helpers';
1312
import { showAndLogErrorMessage } from './helpers';
1413
import { assertNever } from './helpers-pure';
1514
import { FromResultsViewMsg, Interpretation, IntoResultsViewMsg, ResultsPaths, SortedResultSetInfo, SortedResultsMap, INTERPRETED_RESULTS_PER_RUN_LIMIT, QueryMetadata } from './interface-types';
1615
import { Logger } from './logging';
1716
import * as messages from './messages';
18-
import { EvaluationInfo, interpretResults, QueryInfo, tmpDir } from './queries';
17+
import { QueryInfo, tmpDir } from './queries';
18+
import { CompletedQuery, interpretResults } from './query-results';
1919

2020
/**
2121
* interface.ts
@@ -87,7 +87,7 @@ export function webviewUriToFileUri(webviewUri: string): Uri {
8787
}
8888

8989
export class InterfaceManager extends DisposableObject {
90-
private _displayedEvaluationInfo?: EvaluationInfo;
90+
private _displayedQuery?: CompletedQuery;
9191
private _panel: vscode.WebviewPanel | undefined;
9292
private _panelLoaded = false;
9393
private _panelLoadedCallBacks: (() => void)[] = [];
@@ -180,14 +180,14 @@ export class InterfaceManager extends DisposableObject {
180180
this._panelLoadedCallBacks = [];
181181
break;
182182
case 'changeSort': {
183-
if (this._displayedEvaluationInfo === undefined) {
183+
if (this._displayedQuery === undefined) {
184184
showAndLogErrorMessage("Failed to sort results since evaluation info was unknown.");
185185
break;
186186
}
187187
// Notify the webview that it should expect new results.
188188
await this.postMessage({ t: 'resultsUpdating' });
189-
await this._displayedEvaluationInfo.query.updateSortState(this.cliServer, msg.resultSetName, msg.sortState);
190-
await this.showResults(this._displayedEvaluationInfo, WebviewReveal.NotForced, true);
189+
await this._displayedQuery.updateSortState(this.cliServer, msg.resultSetName, msg.sortState);
190+
await this.showResults(this._displayedQuery, WebviewReveal.NotForced, true);
191191
break;
192192
}
193193
default:
@@ -211,25 +211,25 @@ export class InterfaceManager extends DisposableObject {
211211

212212
/**
213213
* Show query results in webview panel.
214-
* @param info Evaluation info for the executed query.
214+
* @param results Evaluation info for the executed query.
215215
* @param shouldKeepOldResultsWhileRendering Should keep old results while rendering.
216216
* @param forceReveal Force the webview panel to be visible and
217217
* Appropriate when the user has just performed an explicit
218218
* UI interaction requesting results, e.g. clicking on a query
219219
* history entry.
220220
*/
221-
public async showResults(info: EvaluationInfo, forceReveal: WebviewReveal, shouldKeepOldResultsWhileRendering: boolean = false): Promise<void> {
222-
if (info.result.resultType !== messages.QueryResultType.SUCCESS) {
221+
public async showResults(results: CompletedQuery, forceReveal: WebviewReveal, shouldKeepOldResultsWhileRendering: boolean = false): Promise<void> {
222+
if (results.result.resultType !== messages.QueryResultType.SUCCESS) {
223223
return;
224224
}
225225

226-
const interpretation = await this.interpretResultsInfo(info.query, info.query.resultsPaths);
226+
const interpretation = await this.interpretResultsInfo(results.query);
227227

228228
const sortedResultsMap: SortedResultsMap = {};
229-
info.query.sortedResultsInfo.forEach((v, k) =>
229+
results.sortedResultsInfo.forEach((v, k) =>
230230
sortedResultsMap[k] = this.convertPathPropertiesToWebviewUris(v));
231231

232-
this._displayedEvaluationInfo = info;
232+
this._displayedQuery = results;
233233

234234
const panel = this.getPanel();
235235
await this.waitForPanelLoaded();
@@ -242,7 +242,7 @@ export class InterfaceManager extends DisposableObject {
242242
// more asynchronous message to not so abruptly interrupt
243243
// user's workflow by immediately revealing the panel.
244244
const showButton = 'View Results';
245-
const queryName = helpers.getQueryName(info);
245+
const queryName = results.queryName;
246246
const resultPromise = vscode.window.showInformationMessage(
247247
`Finished running query ${(queryName.length > 0) ? ` “${queryName}”` : ''}.`,
248248
showButton
@@ -259,37 +259,37 @@ export class InterfaceManager extends DisposableObject {
259259
await this.postMessage({
260260
t: 'setState',
261261
interpretation,
262-
origResultsPaths: info.query.resultsPaths,
263-
resultsPath: this.convertPathToWebviewUri(info.query.resultsPaths.resultsPath),
262+
origResultsPaths: results.query.resultsPaths,
263+
resultsPath: this.convertPathToWebviewUri(results.query.resultsPaths.resultsPath),
264264
sortedResultsMap,
265-
database: info.database,
265+
database: results.database,
266266
shouldKeepOldResultsWhileRendering,
267-
metadata: info.query.metadata
267+
metadata: results.query.metadata
268268
});
269269
}
270270

271-
private async getTruncatedResults(metadata : QueryMetadata | undefined ,resultsInfo: ResultsPaths, sourceInfo : cli.SourceInfo | undefined, sourceLocationPrefix : string ) : Promise<Interpretation> {
272-
const sarif = await interpretResults(this.cliServer, metadata, resultsInfo, sourceInfo);
273-
// For performance reasons, limit the number of results we try
274-
// to serialize and send to the webview. TODO: possibly also
275-
// limit number of paths per result, number of steps per path,
276-
// or throw an error if we are in aggregate trying to send
277-
// massively too much data, as it can make the extension
278-
// unresponsive.
279-
let numTruncatedResults = 0;
280-
sarif.runs.forEach(run => {
281-
if (run.results !== undefined) {
282-
if (run.results.length > INTERPRETED_RESULTS_PER_RUN_LIMIT) {
283-
numTruncatedResults += run.results.length - INTERPRETED_RESULTS_PER_RUN_LIMIT;
284-
run.results = run.results.slice(0, INTERPRETED_RESULTS_PER_RUN_LIMIT);
271+
private async getTruncatedResults(metadata: QueryMetadata | undefined, resultsPaths: ResultsPaths, sourceInfo: cli.SourceInfo | undefined, sourceLocationPrefix: string): Promise<Interpretation> {
272+
const sarif = await interpretResults(this.cliServer, metadata, resultsPaths.interpretedResultsPath, sourceInfo);
273+
// For performance reasons, limit the number of results we try
274+
// to serialize and send to the webview. TODO: possibly also
275+
// limit number of paths per result, number of steps per path,
276+
// or throw an error if we are in aggregate trying to send
277+
// massively too much data, as it can make the extension
278+
// unresponsive.
279+
let numTruncatedResults = 0;
280+
sarif.runs.forEach(run => {
281+
if (run.results !== undefined) {
282+
if (run.results.length > INTERPRETED_RESULTS_PER_RUN_LIMIT) {
283+
numTruncatedResults += run.results.length - INTERPRETED_RESULTS_PER_RUN_LIMIT;
284+
run.results = run.results.slice(0, INTERPRETED_RESULTS_PER_RUN_LIMIT);
285+
}
285286
}
286-
}
287-
});
288-
return { sarif, sourceLocationPrefix, numTruncatedResults };
289-
;
290-
}
287+
});
288+
return { sarif, sourceLocationPrefix, numTruncatedResults };
289+
;
290+
}
291291

292-
private async interpretResultsInfo(query: QueryInfo, resultsInfo: ResultsPaths): Promise<Interpretation | undefined> {
292+
private async interpretResultsInfo(query: QueryInfo): Promise<Interpretation | undefined> {
293293
let interpretation: Interpretation | undefined = undefined;
294294
if (query.hasInterpretedResults()
295295
&& query.quickEvalPosition === undefined // never do results interpretation if quickEval
@@ -300,7 +300,7 @@ private async getTruncatedResults(metadata : QueryMetadata | undefined ,resultsI
300300
const sourceInfo = sourceArchiveUri === undefined ?
301301
undefined :
302302
{ sourceArchive: sourceArchiveUri.fsPath, sourceLocationPrefix };
303-
interpretation = await this.getTruncatedResults(query.metadata, resultsInfo, sourceInfo, sourceLocationPrefix);
303+
interpretation = await this.getTruncatedResults(query.metadata, query.resultsPaths, sourceInfo, sourceLocationPrefix);
304304
}
305305
catch (e) {
306306
// If interpretation fails, accept the error and continue
@@ -331,7 +331,7 @@ private async getTruncatedResults(metadata : QueryMetadata | undefined ,resultsI
331331

332332
}
333333

334-
private async showProblemResultsAsDiagnostics(interpretation : Interpretation, databaseItem: DatabaseItem): Promise<void> {
334+
private async showProblemResultsAsDiagnostics(interpretation: Interpretation, databaseItem: DatabaseItem): Promise<void> {
335335
const { sarif, sourceLocationPrefix } = interpretation;
336336

337337

@@ -443,8 +443,8 @@ async function showLocation(loc: ResolvableLocationValue, databaseItem: Database
443443
const doc = await workspace.openTextDocument(resolvedLocation.uri);
444444
const editorsWithDoc = Window.visibleTextEditors.filter(e => e.document === doc);
445445
const editor = editorsWithDoc.length > 0
446-
? editorsWithDoc[0]
447-
: await Window.showTextDocument(doc, vscode.ViewColumn.One);
446+
? editorsWithDoc[0]
447+
: await Window.showTextDocument(doc, vscode.ViewColumn.One);
448448
let range = resolvedLocation.range;
449449
// When highlighting the range, vscode's occurrence-match and bracket-match highlighting will
450450
// trigger based on where we place the cursor/selection, and will compete for the user's attention.
@@ -457,8 +457,8 @@ async function showLocation(loc: ResolvableLocationValue, databaseItem: Database
457457
// For multi-line ranges, place the cursor at the beginning to avoid visual artifacts from selected line-breaks.
458458
// Multi-line ranges are usually large enough to overshadow the noise from bracket highlighting.
459459
let selectionEnd = (range.start.line === range.end.line)
460-
? range.end
461-
: range.start;
460+
? range.end
461+
: range.start;
462462
editor.selection = new vscode.Selection(range.start, selectionEnd);
463463
editor.revealRange(range, vscode.TextEditorRevealType.InCenter);
464464
editor.setDecorations(shownLocationDecoration, [range]);

0 commit comments

Comments
 (0)