Use sarif for problems view over doing it manually.
This commit is contained in:
@@ -6,7 +6,7 @@ import * as util from 'util';
|
||||
import { Logger, ProgressReporter } from "./logging";
|
||||
import { Disposable } from "vscode";
|
||||
import { DistributionProvider } from "./distribution";
|
||||
import { SortDirection } from "./interface-types";
|
||||
import { SortDirection, QueryMetadata } from "./interface-types";
|
||||
import { assertNever } from "./helpers-pure";
|
||||
|
||||
/**
|
||||
@@ -55,16 +55,6 @@ export interface UpgradesInfo {
|
||||
*/
|
||||
export type QlpacksInfo = { [name: string]: string[] };
|
||||
|
||||
/**
|
||||
* The expected output of `codeql resolve metadata`.
|
||||
*/
|
||||
export interface QueryMetadata {
|
||||
name?: string,
|
||||
description?: string,
|
||||
id?: string,
|
||||
kind?: string
|
||||
}
|
||||
|
||||
// `codeql bqrs interpret` requires both of these to be present or
|
||||
// both absent.
|
||||
export interface SourceInfo {
|
||||
@@ -159,7 +149,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
if (!config) {
|
||||
throw new Error("Failed to find codeql distribution")
|
||||
}
|
||||
return spawnServer(config, "CodeQL CLI Server", ["execute", "cli-server"], [], this.logger, _data => {})
|
||||
return spawnServer(config, "CodeQL CLI Server", ["execute", "cli-server"], [], this.logger, _data => { })
|
||||
}
|
||||
|
||||
private async runCodeQlCliInternal(command: string[], commandArgs: string[], description: string): Promise<string> {
|
||||
|
||||
@@ -16,6 +16,14 @@ export interface DatabaseInfo {
|
||||
databaseUri: string;
|
||||
}
|
||||
|
||||
/** Arbitrary query metadata */
|
||||
export interface QueryMetadata {
|
||||
name?: string,
|
||||
description?: string,
|
||||
id?: string,
|
||||
kind?: string
|
||||
}
|
||||
|
||||
export interface PreviousExecution {
|
||||
queryName: string;
|
||||
time: string;
|
||||
@@ -31,7 +39,6 @@ export interface Interpretation {
|
||||
|
||||
export interface ResultsInfo {
|
||||
resultsPath: string;
|
||||
interpretedResultsPath: string;
|
||||
}
|
||||
|
||||
export interface SortedResultSetInfo {
|
||||
@@ -56,7 +63,7 @@ export interface SetStateMsg {
|
||||
sortedResultsMap: SortedResultsMap;
|
||||
interpretation: undefined | Interpretation;
|
||||
database: DatabaseInfo;
|
||||
kind?: string;
|
||||
metadata?: QueryMetadata
|
||||
/**
|
||||
* Whether to keep displaying the old results while rendering the new results.
|
||||
*
|
||||
@@ -86,6 +93,7 @@ interface ViewSourceFileMsg {
|
||||
interface ToggleDiagnostics {
|
||||
t: 'toggleDiagnostics';
|
||||
databaseUri: string;
|
||||
metadata?: QueryMetadata
|
||||
resultsPath: string;
|
||||
visible: boolean;
|
||||
kind?: string;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as crypto from 'crypto';
|
||||
import * as path from 'path';
|
||||
import * as bqrs from 'semmle-bqrs';
|
||||
import { CustomResultSets, FivePartLocation, LocationStyle, LocationValue, PathProblemQueryResults, ProblemQueryResults, ResolvableLocationValue, tryGetResolvableLocation, WholeFileLocation } from 'semmle-bqrs';
|
||||
import { FileReader } from 'semmle-io-node';
|
||||
import * as cli from './cli';
|
||||
import * as Sarif from 'sarif';
|
||||
import { parseSarifLocation, parseSarifPlainTextMessage } from './sarif-utils';
|
||||
import { FivePartLocation, LocationValue, ResolvableLocationValue, WholeFileLocation, tryGetResolvableLocation, LocationStyle } from 'semmle-bqrs';
|
||||
import { DisposableObject } from 'semmle-vscode-utils';
|
||||
import * as vscode from 'vscode';
|
||||
import { Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, languages, Location, Position, Range, Uri, window as Window, workspace } from 'vscode';
|
||||
@@ -11,7 +12,7 @@ import { DatabaseItem, DatabaseManager } from './databases';
|
||||
import * as helpers from './helpers';
|
||||
import { showAndLogErrorMessage } from './helpers';
|
||||
import { assertNever } from './helpers-pure';
|
||||
import { FromResultsViewMsg, Interpretation, IntoResultsViewMsg, ResultsInfo, SortedResultSetInfo, SortedResultsMap, INTERPRETED_RESULTS_PER_RUN_LIMIT } from './interface-types';
|
||||
import { FromResultsViewMsg, Interpretation, IntoResultsViewMsg, ResultsInfo, SortedResultSetInfo, SortedResultsMap, INTERPRETED_RESULTS_PER_RUN_LIMIT, QueryMetadata } from './interface-types';
|
||||
import { Logger } from './logging';
|
||||
import * as messages from './messages';
|
||||
import { EvaluationInfo, interpretResults, QueryInfo, tmpDir } from './queries';
|
||||
@@ -165,7 +166,7 @@ export class InterfaceManager extends DisposableObject {
|
||||
if (msg.visible) {
|
||||
const databaseItem = this.databaseManager.findDatabaseItem(Uri.parse(msg.databaseUri));
|
||||
if (databaseItem !== undefined) {
|
||||
await this.showResultsAsDiagnostics(msg.resultsPath, msg.kind, databaseItem);
|
||||
await this.showResultsAsDiagnostics(msg.resultsPath, msg.metadata, databaseItem);
|
||||
}
|
||||
} else {
|
||||
// TODO: Only clear diagnostics on the same database.
|
||||
@@ -262,10 +263,31 @@ export class InterfaceManager extends DisposableObject {
|
||||
sortedResultsMap,
|
||||
database: info.database,
|
||||
shouldKeepOldResultsWhileRendering,
|
||||
kind: info.query.metadata ? info.query.metadata.kind : undefined
|
||||
metadata: info.query.metadata
|
||||
});
|
||||
}
|
||||
|
||||
private async getTruncatedResults(metadata : QueryMetadata | undefined ,resultsPathOnDisk: string, sourceInfo : cli.SourceInfo | undefined, sourceLocationPrefix : string ) : Promise<Interpretation> {
|
||||
const sarif = await interpretResults(this.cliServer, metadata, resultsPathOnDisk, sourceInfo);
|
||||
// For performance reasons, limit the number of results we try
|
||||
// to serialize and send to the webview. TODO: possibly also
|
||||
// limit number of paths per result, number of steps per path,
|
||||
// or throw an error if we are in aggregate trying to send
|
||||
// massively too much data, as it can make the extension
|
||||
// unresponsive.
|
||||
let numTruncatedResults = 0;
|
||||
sarif.runs.forEach(run => {
|
||||
if (run.results !== undefined) {
|
||||
if (run.results.length > INTERPRETED_RESULTS_PER_RUN_LIMIT) {
|
||||
numTruncatedResults += run.results.length - INTERPRETED_RESULTS_PER_RUN_LIMIT;
|
||||
run.results = run.results.slice(0, INTERPRETED_RESULTS_PER_RUN_LIMIT);
|
||||
}
|
||||
}
|
||||
});
|
||||
return { sarif, sourceLocationPrefix, numTruncatedResults };
|
||||
;
|
||||
}
|
||||
|
||||
private async interpretResultsInfo(query: QueryInfo, resultsInfo: ResultsInfo): Promise<Interpretation | undefined> {
|
||||
let interpretation: Interpretation | undefined = undefined;
|
||||
if (query.hasInterpretedResults()
|
||||
@@ -277,23 +299,7 @@ export class InterfaceManager extends DisposableObject {
|
||||
const sourceInfo = sourceArchiveUri === undefined ?
|
||||
undefined :
|
||||
{ sourceArchive: sourceArchiveUri.fsPath, sourceLocationPrefix };
|
||||
const sarif = await interpretResults(this.cliServer, query, resultsInfo, sourceInfo);
|
||||
// For performance reasons, limit the number of results we try
|
||||
// to serialize and send to the webview. TODO: possibly also
|
||||
// limit number of paths per result, number of steps per path,
|
||||
// or throw an error if we are in aggregate trying to send
|
||||
// massively too much data, as it can make the extension
|
||||
// unresponsive.
|
||||
let numTruncatedResults = 0;
|
||||
sarif.runs.forEach(run => {
|
||||
if (run.results !== undefined) {
|
||||
if (run.results.length > INTERPRETED_RESULTS_PER_RUN_LIMIT) {
|
||||
numTruncatedResults += run.results.length - INTERPRETED_RESULTS_PER_RUN_LIMIT;
|
||||
run.results = run.results.slice(0, INTERPRETED_RESULTS_PER_RUN_LIMIT);
|
||||
}
|
||||
}
|
||||
});
|
||||
interpretation = { sarif, sourceLocationPrefix, numTruncatedResults };
|
||||
interpretation = await this.getTruncatedResults(query.metadata, resultsInfo.resultsPath, sourceInfo, sourceLocationPrefix);
|
||||
}
|
||||
catch (e) {
|
||||
// If interpretation fails, accept the error and continue
|
||||
@@ -301,90 +307,99 @@ export class InterfaceManager extends DisposableObject {
|
||||
this.logger.log(`Exception during results interpretation: ${e.message}. Will show raw results instead.`);
|
||||
}
|
||||
}
|
||||
|
||||
return interpretation;
|
||||
}
|
||||
|
||||
private async showResultsAsDiagnostics(resultsPath: string, kind: string | undefined,
|
||||
database: DatabaseItem) {
|
||||
|
||||
private async showResultsAsDiagnostics(webviewResultsUri: string, metadata: QueryMetadata | undefined, database: DatabaseItem) {
|
||||
// URIs from the webview have the vscode-resource scheme, so convert into a filesystem URI first.
|
||||
const resultsPathOnDisk = webviewUriToFileUri(resultsPath).fsPath;
|
||||
const fileReader = await FileReader.open(resultsPathOnDisk);
|
||||
const resultsPathOnDisk = webviewUriToFileUri(webviewResultsUri).fsPath;
|
||||
const sourceLocationPrefix = await database.getSourceLocationPrefix(this.cliServer);
|
||||
const sourceArchiveUri = database.sourceArchive;
|
||||
const sourceInfo = sourceArchiveUri === undefined ?
|
||||
undefined :
|
||||
{ sourceArchive: sourceArchiveUri.fsPath, sourceLocationPrefix };
|
||||
const interpretation = await this.getTruncatedResults(metadata, resultsPathOnDisk, sourceInfo, sourceLocationPrefix);
|
||||
|
||||
try {
|
||||
const resultSets = await bqrs.open(fileReader);
|
||||
try {
|
||||
switch (kind || 'problem') {
|
||||
case 'problem': {
|
||||
const customResults = bqrs.createCustomResultSets<ProblemQueryResults>(resultSets, ProblemQueryResults);
|
||||
await this.showProblemResultsAsDiagnostics(customResults, database);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'path-problem': {
|
||||
const customResults = bqrs.createCustomResultSets<PathProblemQueryResults>(resultSets, PathProblemQueryResults);
|
||||
await this.showProblemResultsAsDiagnostics(customResults, database);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unrecognized query kind '${kind}'.`);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
const msg = e instanceof Error ? e.message : e.toString();
|
||||
this.logger.log(`Exception while computing problem results as diagnostics: ${msg}`);
|
||||
this._diagnosticCollection.clear();
|
||||
}
|
||||
await this.showProblemResultsAsDiagnostics(interpretation, database);
|
||||
}
|
||||
finally {
|
||||
fileReader.dispose();
|
||||
catch (e) {
|
||||
const msg = e instanceof Error ? e.message : e.toString();
|
||||
this.logger.log(`Exception while computing problem results as diagnostics: ${msg}`);
|
||||
this._diagnosticCollection.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async showProblemResultsAsDiagnostics(results: CustomResultSets<ProblemQueryResults>,
|
||||
databaseItem: DatabaseItem): Promise<void> {
|
||||
private async showProblemResultsAsDiagnostics(interpretation : Interpretation, databaseItem: DatabaseItem): Promise<void> {
|
||||
const { sarif, sourceLocationPrefix } = interpretation;
|
||||
|
||||
|
||||
if (!sarif.runs || !sarif.runs[0].results) {
|
||||
this.logger.log("Didn't find a run in the sarif results. Error processing sarif?")
|
||||
return;
|
||||
}
|
||||
|
||||
const diagnostics: [Uri, ReadonlyArray<Diagnostic>][] = [];
|
||||
for await (const problemRow of results.problems.readTuples()) {
|
||||
const codeLocation = resolveLocation(problemRow.element.location, databaseItem);
|
||||
let message: string;
|
||||
const references = problemRow.references;
|
||||
if (references) {
|
||||
let referenceIndex = 0;
|
||||
message = problemRow.message.replace(/\$\@/g, sub => {
|
||||
if (referenceIndex < references.length) {
|
||||
const replacement = references[referenceIndex].text;
|
||||
referenceIndex++;
|
||||
return replacement;
|
||||
}
|
||||
else {
|
||||
return sub;
|
||||
}
|
||||
});
|
||||
|
||||
for (const result of sarif.runs[0].results) {
|
||||
const message = result.message.text;
|
||||
if (message === undefined) {
|
||||
this.logger.log("Sarif had result without plaintext message")
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
message = problemRow.message;
|
||||
if (!result.locations) {
|
||||
this.logger.log("Sarif had result without location")
|
||||
continue;
|
||||
}
|
||||
const diagnostic = new Diagnostic(codeLocation.range, message, DiagnosticSeverity.Warning);
|
||||
if (problemRow.references) {
|
||||
const relatedInformation: DiagnosticRelatedInformation[] = [];
|
||||
for (const reference of problemRow.references) {
|
||||
const referenceLocation = tryResolveLocation(reference.element.location, databaseItem);
|
||||
|
||||
const sarifLoc = parseSarifLocation(result.locations[0], sourceLocationPrefix);
|
||||
if (sarifLoc.t == "NoLocation") {
|
||||
continue;
|
||||
}
|
||||
const resultLocation = tryResolveLocation(sarifLoc, databaseItem)
|
||||
if (!resultLocation) {
|
||||
this.logger.log("Sarif location was not resolvable " + sarifLoc)
|
||||
continue;
|
||||
}
|
||||
const parsedMessage = parseSarifPlainTextMessage(message);
|
||||
const relatedInformation: DiagnosticRelatedInformation[] = [];
|
||||
const relatedLocationsById: { [k: number]: Sarif.Location } = {};
|
||||
|
||||
|
||||
for (let loc of result.relatedLocations || []) {
|
||||
relatedLocationsById[loc.id!] = loc;
|
||||
}
|
||||
let resultMessageChunks: string[] = [];
|
||||
for (const section of parsedMessage) {
|
||||
if (typeof section === "string") {
|
||||
resultMessageChunks.push(section);
|
||||
} else {
|
||||
resultMessageChunks.push(section.text);
|
||||
const sarifChunkLoc = parseSarifLocation(relatedLocationsById[section.dest], sourceLocationPrefix);
|
||||
if (sarifChunkLoc.t == "NoLocation") {
|
||||
continue;
|
||||
}
|
||||
const referenceLocation = tryResolveLocation(sarifChunkLoc, databaseItem);
|
||||
|
||||
|
||||
if (referenceLocation) {
|
||||
const related = new DiagnosticRelatedInformation(referenceLocation,
|
||||
reference.text);
|
||||
section.text);
|
||||
relatedInformation.push(related);
|
||||
}
|
||||
}
|
||||
diagnostic.relatedInformation = relatedInformation;
|
||||
}
|
||||
const diagnostic = new Diagnostic(resultLocation.range, resultMessageChunks.join(""), DiagnosticSeverity.Warning);
|
||||
diagnostic.relatedInformation = relatedInformation;
|
||||
|
||||
diagnostics.push([
|
||||
codeLocation.uri,
|
||||
resultLocation.uri,
|
||||
[diagnostic]
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
this._diagnosticCollection.set(diagnostics);
|
||||
}
|
||||
|
||||
@@ -479,22 +494,6 @@ function resolveWholeFileLocation(loc: WholeFileLocation, databaseItem: Database
|
||||
return new Location(databaseItem.resolveSourceFile(loc.file), range);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the specified CodeQL location to a URI into the source archive.
|
||||
* @param loc CodeQL location to resolve
|
||||
* @param databaseItem Database in which to resolve the file location.
|
||||
*/
|
||||
function resolveLocation(loc: LocationValue | undefined, databaseItem: DatabaseItem): Location {
|
||||
const resolvedLocation = tryResolveLocation(loc, databaseItem);
|
||||
if (resolvedLocation) {
|
||||
return resolvedLocation;
|
||||
}
|
||||
else {
|
||||
// Return a fake position in the source archive directory itself.
|
||||
return new Location(databaseItem.resolveSourceFile(undefined), new Position(0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to resolve the specified CodeQL location to a URI into the source archive. If no exact location
|
||||
* can be resolved, returns `undefined`.
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as vscode from 'vscode';
|
||||
import * as cli from './cli';
|
||||
import { DatabaseItem, getUpgradesDirectories } from './databases';
|
||||
import * as helpers from './helpers';
|
||||
import { DatabaseInfo, SortState, ResultsInfo, SortedResultSetInfo } from './interface-types';
|
||||
import { DatabaseInfo, SortState, ResultsInfo, SortedResultSetInfo, QueryMetadata } from './interface-types';
|
||||
import { logger } from './logging';
|
||||
import * as messages from './messages';
|
||||
import * as qsClient from './queryserver-client';
|
||||
@@ -64,13 +64,12 @@ export class QueryInfo {
|
||||
public dbItem: DatabaseItem,
|
||||
public queryDbscheme: string, // the dbscheme file the query expects, based on library path resolution
|
||||
public quickEvalPosition?: messages.Position,
|
||||
public metadata?: cli.QueryMetadata,
|
||||
public metadata?: QueryMetadata,
|
||||
) {
|
||||
this.queryId = QueryInfo.nextQueryId++;
|
||||
this.compiledQueryPath = path.join(tmpDir.name, `compiledQuery${this.queryId}.qlo`);
|
||||
this.resultsInfo = {
|
||||
resultsPath: path.join(tmpDir.name, `results${this.queryId}.bqrs`),
|
||||
interpretedResultsPath: path.join(tmpDir.name, `interpretedResults${this.queryId}.sarif`)
|
||||
};
|
||||
this.sortedResultsInfo = new Map();
|
||||
if (dbItem.contents === undefined) {
|
||||
@@ -186,11 +185,12 @@ export class QueryInfo {
|
||||
/**
|
||||
* Call cli command to interpret results.
|
||||
*/
|
||||
export async function interpretResults(server: cli.CodeQLCliServer, queryInfo: QueryInfo, resultsInfo: ResultsInfo, sourceInfo?: cli.SourceInfo): Promise<sarif.Log> {
|
||||
if (await fs.pathExists(resultsInfo.interpretedResultsPath)) {
|
||||
return JSON.parse(await fs.readFile(resultsInfo.interpretedResultsPath, 'utf8'));
|
||||
export async function interpretResults(server: cli.CodeQLCliServer, metadata: QueryMetadata | undefined, resultsPath: string, sourceInfo?: cli.SourceInfo): Promise<sarif.Log> {
|
||||
const interpretedResultsPath = resultsPath + ".interpreted.sarif"
|
||||
|
||||
if (await fs.pathExists(interpretedResultsPath)) {
|
||||
return JSON.parse(await fs.readFile(interpretedResultsPath, 'utf8'));
|
||||
}
|
||||
const { metadata } = queryInfo;
|
||||
if (metadata == undefined) {
|
||||
throw new Error('Can\'t interpret results without query metadata');
|
||||
}
|
||||
@@ -200,14 +200,10 @@ export async function interpretResults(server: cli.CodeQLCliServer, queryInfo: Q
|
||||
}
|
||||
if (id == undefined) {
|
||||
// Interpretation per se doesn't really require an id, but the
|
||||
// SARIF format does, so in the absence of one, we invent one
|
||||
// based on the query path.
|
||||
//
|
||||
// Just to be careful, sanitize to remove '/' since SARIF (section
|
||||
// 3.27.5 "ruleId property") says that it has special meaning.
|
||||
id = queryInfo.program.queryPath.replace(/\//g, '-');
|
||||
// SARIF format does, so in the absence of one, we use a dummy id.
|
||||
id = "dummy-id";
|
||||
}
|
||||
return await server.interpretBqrs({ kind, id }, resultsInfo.resultsPath, resultsInfo.interpretedResultsPath, sourceInfo);
|
||||
return await server.interpretBqrs( { kind, id }, resultsPath, interpretedResultsPath, sourceInfo);
|
||||
}
|
||||
|
||||
export interface EvaluationInfo {
|
||||
@@ -638,7 +634,7 @@ export async function compileAndRunQueryAgainstDatabase(
|
||||
};
|
||||
|
||||
// Read the query metadata if possible, to use in the UI.
|
||||
let metadata: cli.QueryMetadata | undefined;
|
||||
let metadata: QueryMetadata | undefined;
|
||||
try {
|
||||
metadata = await cliServer.resolveMetadata(qlProgram.queryPath);
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import { LocationValue, ResolvableLocationValue, tryGetResolvableLocation } from 'semmle-bqrs';
|
||||
import { SortState } from '../interface-types';
|
||||
import { SortState, QueryMetadata } from '../interface-types';
|
||||
import { ResultSet, vscode } from './results';
|
||||
|
||||
export interface ResultTableProps {
|
||||
resultSet: ResultSet;
|
||||
databaseUri: string;
|
||||
metadata?: QueryMetadata
|
||||
resultsPath: string | undefined;
|
||||
sortState?: SortState;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { DatabaseInfo, Interpretation, SortState } from '../interface-types';
|
||||
import { DatabaseInfo, Interpretation, SortState, QueryMetadata } from '../interface-types';
|
||||
import { PathTable } from './alert-table';
|
||||
import { RawTable } from './raw-results-table';
|
||||
import { ResultTableProps, tableSelectionHeaderClassName, toggleDiagnosticsClassName } from './result-table-utils';
|
||||
@@ -12,8 +12,8 @@ export interface ResultTablesProps {
|
||||
rawResultSets: readonly ResultSet[];
|
||||
interpretation: Interpretation | undefined;
|
||||
database: DatabaseInfo;
|
||||
metadata? : QueryMetadata
|
||||
resultsPath: string | undefined;
|
||||
kind: string | undefined;
|
||||
sortStates: Map<string, SortState>;
|
||||
isLoadingNewResults: boolean;
|
||||
}
|
||||
@@ -95,7 +95,7 @@ export class ResultTables
|
||||
render(): React.ReactNode {
|
||||
const { selectedTable } = this.state;
|
||||
const resultSets = this.getResultSets();
|
||||
const { database, resultsPath, kind } = this.props;
|
||||
const { database, resultsPath, metadata } = this.props;
|
||||
|
||||
// Only show the Problems view display checkbox for the alerts table.
|
||||
const diagnosticsCheckBox = selectedTable === ALERTS_TABLE_NAME ?
|
||||
@@ -107,7 +107,7 @@ export class ResultTables
|
||||
resultsPath: resultsPath,
|
||||
databaseUri: database.databaseUri,
|
||||
visible: e.target.checked,
|
||||
kind: kind
|
||||
metadata: metadata
|
||||
});
|
||||
}
|
||||
}} />
|
||||
@@ -157,11 +157,9 @@ class ResultTable extends React.Component<ResultTableProps, {}> {
|
||||
const { resultSet } = this.props;
|
||||
switch (resultSet.t) {
|
||||
case 'RawResultSet': return <RawTable
|
||||
resultSet={resultSet} databaseUri={this.props.databaseUri}
|
||||
resultsPath={this.props.resultsPath} sortState={this.props.sortState} />;
|
||||
{...this.props} resultSet={resultSet} />;
|
||||
case 'SarifResultSet': return <PathTable
|
||||
resultSet={resultSet} databaseUri={this.props.databaseUri}
|
||||
resultsPath={this.props.resultsPath} />;
|
||||
{...this.props} resultSet={resultSet} />;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as Rdom from 'react-dom';
|
||||
import * as bqrs from 'semmle-bqrs';
|
||||
import { ElementBase, LocationValue, PrimitiveColumnValue, PrimitiveTypeKind, ResultSetSchema, tryGetResolvableLocation } from 'semmle-bqrs';
|
||||
import { assertNever } from '../helpers-pure';
|
||||
import { DatabaseInfo, FromResultsViewMsg, Interpretation, IntoResultsViewMsg, SortedResultSetInfo, SortState, NavigatePathMsg } from '../interface-types';
|
||||
import { DatabaseInfo, FromResultsViewMsg, Interpretation, IntoResultsViewMsg, SortedResultSetInfo, SortState, NavigatePathMsg, QueryMetadata } from '../interface-types';
|
||||
import { ResultTables } from './result-tables';
|
||||
import { EventHandlers as EventHandlerList } from './event-handler-list';
|
||||
|
||||
@@ -127,7 +127,6 @@ async function parseResultSets(response: Response): Promise<readonly ResultSet[]
|
||||
|
||||
interface ResultsInfo {
|
||||
resultsPath: string;
|
||||
kind: string | undefined;
|
||||
database: DatabaseInfo;
|
||||
interpretation: Interpretation | undefined;
|
||||
sortedResultsMap: Map<string, SortedResultSetInfo>;
|
||||
@@ -135,6 +134,7 @@ interface ResultsInfo {
|
||||
* See {@link SetStateMsg.shouldKeepOldResultsWhileRendering}.
|
||||
*/
|
||||
shouldKeepOldResultsWhileRendering: boolean;
|
||||
metadata?: QueryMetadata
|
||||
}
|
||||
|
||||
interface Results {
|
||||
@@ -186,11 +186,11 @@ class App extends React.Component<{}, ResultsViewState> {
|
||||
case 'setState':
|
||||
this.updateStateWithNewResultsInfo({
|
||||
resultsPath: msg.resultsPath,
|
||||
kind: msg.kind,
|
||||
sortedResultsMap: new Map(Object.entries(msg.sortedResultsMap)),
|
||||
database: msg.database,
|
||||
interpretation: msg.interpretation,
|
||||
shouldKeepOldResultsWhileRendering: msg.shouldKeepOldResultsWhileRendering
|
||||
shouldKeepOldResultsWhileRendering: msg.shouldKeepOldResultsWhileRendering,
|
||||
metadata: msg.metadata
|
||||
});
|
||||
|
||||
this.loadResults();
|
||||
@@ -309,7 +309,7 @@ class App extends React.Component<{}, ResultsViewState> {
|
||||
interpretation={displayedResults.resultsInfo ? displayedResults.resultsInfo.interpretation : undefined}
|
||||
database={displayedResults.results.database}
|
||||
resultsPath={displayedResults.resultsInfo ? displayedResults.resultsInfo.resultsPath : undefined}
|
||||
kind={displayedResults.resultsInfo ? displayedResults.resultsInfo.kind : undefined}
|
||||
metadata={displayedResults.resultsInfo ? displayedResults.resultsInfo.metadata : undefined}
|
||||
sortStates={displayedResults.results.sortStates}
|
||||
isLoadingNewResults={this.state.isExpectingResultsUpdate || this.state.nextResultsInfo !== null} />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user