Add 'show next/previous alert' commands
This commit is contained in:
@@ -601,6 +601,14 @@
|
||||
"command": "codeQLQueryResults.previousPathStep",
|
||||
"title": "CodeQL: Show Previous Step on Path"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryResults.nextAlert",
|
||||
"title": "CodeQL: Show Next Alert"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryResults.previousAlert",
|
||||
"title": "CodeQL: Show Previous Alert"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.restartQueryServer",
|
||||
"title": "CodeQL: Restart Query Server"
|
||||
|
||||
@@ -154,6 +154,18 @@ export class ResultsView extends AbstractWebview<IntoResultsViewMsg, FromResults
|
||||
this.navigatePathStep.bind(this, -1)
|
||||
)
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
'codeQLQueryResults.nextAlert',
|
||||
this.navigateAlert.bind(this, 1)
|
||||
)
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
'codeQLQueryResults.previousAlert',
|
||||
this.navigateAlert.bind(this, -1)
|
||||
)
|
||||
);
|
||||
|
||||
this.push(
|
||||
this.databaseManager.onDidChangeDatabaseItem(({ kind }) => {
|
||||
@@ -173,6 +185,10 @@ export class ResultsView extends AbstractWebview<IntoResultsViewMsg, FromResults
|
||||
await this.postMessage({ t: 'navigatePath', direction });
|
||||
}
|
||||
|
||||
async navigateAlert(direction: number): Promise<void> {
|
||||
await this.postMessage({ t: 'navigateAlert', direction });
|
||||
}
|
||||
|
||||
protected getPanelConfig(): WebviewPanelConfig {
|
||||
return {
|
||||
viewId: 'resultsView',
|
||||
|
||||
@@ -141,7 +141,7 @@ export interface ShowInterpretedPageMsg {
|
||||
queryPath: string;
|
||||
}
|
||||
|
||||
/** Advance to the next or previous path no in the path viewer */
|
||||
/** Advance to the next or previous path step in the path viewer */
|
||||
export interface NavigatePathMsg {
|
||||
t: 'navigatePath';
|
||||
|
||||
@@ -149,6 +149,14 @@ export interface NavigatePathMsg {
|
||||
direction: number;
|
||||
}
|
||||
|
||||
/** Advance to the next or previous alert in the path viewer */
|
||||
export interface NavigateAlertMsg {
|
||||
t: 'navigateAlert';
|
||||
|
||||
/** 1 for next, -1 for previous */
|
||||
direction: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A message indicating that the results view should untoggle the
|
||||
* "Show results in Problems view" checkbox.
|
||||
@@ -165,6 +173,7 @@ export type IntoResultsViewMsg =
|
||||
| SetStateMsg
|
||||
| ShowInterpretedPageMsg
|
||||
| NavigatePathMsg
|
||||
| NavigateAlertMsg
|
||||
| UntoggleShowProblemsMsg;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,33 +1,48 @@
|
||||
import * as sarif from 'sarif';
|
||||
|
||||
/**
|
||||
* Identifies a result, a path, or one of the nodes on a path.
|
||||
*/
|
||||
interface ResultKeyBase {
|
||||
resultIndex: number;
|
||||
pathIndex?: number;
|
||||
pathNodeIndex?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies one of the results in a result set by its index in the result list.
|
||||
*/
|
||||
export interface Result {
|
||||
export interface Result extends ResultKeyBase {
|
||||
resultIndex: number;
|
||||
pathIndex?: undefined;
|
||||
pathNodeIndex?: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies one of the paths associated with a result.
|
||||
*/
|
||||
export interface Path extends Result {
|
||||
export interface Path extends ResultKeyBase {
|
||||
pathIndex: number;
|
||||
pathNodeIndex?: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies one of the nodes in a path.
|
||||
*/
|
||||
export interface PathNode extends Path {
|
||||
export interface PathNode extends ResultKeyBase {
|
||||
pathIndex: number;
|
||||
pathNodeIndex: number;
|
||||
}
|
||||
|
||||
export type ResultKey = Result | Path | PathNode;
|
||||
|
||||
/** Alias for `undefined` but more readable in some cases */
|
||||
export const none: PathNode | undefined = undefined;
|
||||
|
||||
/**
|
||||
* Looks up a specific result in a result set.
|
||||
*/
|
||||
export function getResult(sarif: sarif.Log, key: Result): sarif.Result | undefined {
|
||||
export function getResult(sarif: sarif.Log, key: Result | Path | PathNode): sarif.Result | undefined {
|
||||
if (sarif.runs.length === 0) return undefined;
|
||||
if (sarif.runs[0].results === undefined) return undefined;
|
||||
const results = sarif.runs[0].results;
|
||||
@@ -37,7 +52,7 @@ export function getResult(sarif: sarif.Log, key: Result): sarif.Result | undefin
|
||||
/**
|
||||
* Looks up a specific path in a result set.
|
||||
*/
|
||||
export function getPath(sarif: sarif.Log, key: Path): sarif.ThreadFlow | undefined {
|
||||
export function getPath(sarif: sarif.Log, key: Path | PathNode): sarif.ThreadFlow | undefined {
|
||||
const result = getResult(sarif, key);
|
||||
if (result === undefined) return undefined;
|
||||
let index = -1;
|
||||
@@ -64,7 +79,7 @@ export function getPathNode(sarif: sarif.Log, key: PathNode): sarif.Location | u
|
||||
/**
|
||||
* Returns true if the two keys are both `undefined` or contain the same set of indices.
|
||||
*/
|
||||
export function equals(key1: PathNode | undefined, key2: PathNode | undefined): boolean {
|
||||
export function equals(key1: Partial<PathNode> | undefined, key2: Partial<PathNode> | undefined): boolean {
|
||||
if (key1 === key2) return true;
|
||||
if (key1 === undefined || key2 === undefined) return false;
|
||||
return key1.resultIndex === key2.resultIndex && key1.pathIndex === key2.pathIndex && key1.pathNodeIndex === key2.pathNodeIndex;
|
||||
@@ -73,7 +88,7 @@ export function equals(key1: PathNode | undefined, key2: PathNode | undefined):
|
||||
/**
|
||||
* Returns true if the two keys contain the same set of indices and neither are `undefined`.
|
||||
*/
|
||||
export function equalsNotUndefined(key1: PathNode | undefined, key2: PathNode | undefined): boolean {
|
||||
export function equalsNotUndefined(key1: Partial<PathNode> | undefined, key2: Partial<PathNode> | undefined): boolean {
|
||||
if (key1 === undefined || key2 === undefined) return false;
|
||||
return key1.resultIndex === key2.resultIndex && key1.pathIndex === key2.pathIndex && key1.pathNodeIndex === key2.pathNodeIndex;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as Keys from '../../pure/result-keys';
|
||||
import * as octicons from './octicons';
|
||||
import { className, renderLocation, ResultTableProps, zebraStripe, selectableZebraStripe, jumpToLocation, nextSortDirection, emptyQueryResultsMessage } from './result-table-utils';
|
||||
import { onNavigation, NavigationEvent } from './results';
|
||||
import { InterpretedResultSet, SarifInterpretationData } from '../../pure/interface-types';
|
||||
import { InterpretedResultSet, NavigateAlertMsg, NavigatePathMsg, SarifInterpretationData } from '../../pure/interface-types';
|
||||
import {
|
||||
parseSarifPlainTextMessage,
|
||||
parseSarifLocation,
|
||||
@@ -18,13 +18,13 @@ import { isWholeFileLoc, isLineColumnLoc } from '../../pure/bqrs-utils';
|
||||
export type PathTableProps = ResultTableProps & { resultSet: InterpretedResultSet<SarifInterpretationData> };
|
||||
export interface PathTableState {
|
||||
expanded: { [k: string]: boolean };
|
||||
selectedPathNode: undefined | Keys.PathNode;
|
||||
selectedItem: undefined | Keys.PathNode | Keys.Result;
|
||||
}
|
||||
|
||||
export class PathTable extends React.Component<PathTableProps, PathTableState> {
|
||||
constructor(props: PathTableProps) {
|
||||
super(props);
|
||||
this.state = { expanded: {}, selectedPathNode: undefined };
|
||||
this.state = { expanded: {}, selectedItem: undefined };
|
||||
this.handleNavigationEvent = this.handleNavigationEvent.bind(this);
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
|
||||
const rows: JSX.Element[] = [];
|
||||
const { numTruncatedResults, sourceLocationPrefix } = resultSet.interpretation;
|
||||
|
||||
function renderRelatedLocations(msg: string, relatedLocations: Sarif.Location[]): JSX.Element[] {
|
||||
function renderRelatedLocations(msg: string, relatedLocations: Sarif.Location[], resultKey: Keys.PathNode | Keys.Result | undefined): JSX.Element[] {
|
||||
const relatedLocationsById: { [k: string]: Sarif.Location } = {};
|
||||
for (const loc of relatedLocations) {
|
||||
relatedLocationsById[loc.id!] = loc;
|
||||
@@ -110,7 +110,7 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
|
||||
return <span key={i}>{part}</span>;
|
||||
} else {
|
||||
const renderedLocation = renderSarifLocationWithText(part.text, relatedLocationsById[part.dest],
|
||||
undefined);
|
||||
resultKey);
|
||||
return <span key={i}>{renderedLocation}</span>;
|
||||
}
|
||||
});
|
||||
@@ -122,16 +122,16 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
|
||||
return <span title={locationHint}>{msg}</span>;
|
||||
}
|
||||
|
||||
const updateSelectionCallback = (pathNodeKey: Keys.PathNode | undefined) => {
|
||||
const updateSelectionCallback = (resultKey: Keys.PathNode | Keys.Result | undefined) => {
|
||||
return () => {
|
||||
this.setState(previousState => ({
|
||||
...previousState,
|
||||
selectedPathNode: pathNodeKey
|
||||
selectedItem: resultKey
|
||||
}));
|
||||
};
|
||||
};
|
||||
|
||||
function renderSarifLocationWithText(text: string | undefined, loc: Sarif.Location, pathNodeKey: Keys.PathNode | undefined): JSX.Element | undefined {
|
||||
function renderSarifLocationWithText(text: string | undefined, loc: Sarif.Location, resultKey: Keys.PathNode | Keys.Result | undefined): JSX.Element | undefined {
|
||||
const parsedLoc = parseSarifLocation(loc, sourceLocationPrefix);
|
||||
if ('hint' in parsedLoc) {
|
||||
return renderNonLocation(text, parsedLoc.hint);
|
||||
@@ -141,7 +141,7 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
|
||||
text,
|
||||
databaseUri,
|
||||
undefined,
|
||||
updateSelectionCallback(pathNodeKey)
|
||||
updateSelectionCallback(resultKey)
|
||||
);
|
||||
} else {
|
||||
return undefined;
|
||||
@@ -154,7 +154,7 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
|
||||
*/
|
||||
function renderSarifLocation(
|
||||
loc: Sarif.Location,
|
||||
pathNodeKey: Keys.PathNode | undefined
|
||||
pathNodeKey: Keys.PathNode | Keys.Result | undefined
|
||||
): JSX.Element | undefined {
|
||||
const parsedLoc = parseSarifLocation(loc, sourceLocationPrefix);
|
||||
if ('hint' in parsedLoc) {
|
||||
@@ -195,21 +195,25 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
|
||||
let expansionIndex = 0;
|
||||
|
||||
resultSet.interpretation.data.runs[0].results.forEach((result, resultIndex) => {
|
||||
const resultKey: Keys.Result = { resultIndex };
|
||||
const text = result.message.text || '[no text]';
|
||||
const msg: JSX.Element[] =
|
||||
result.relatedLocations === undefined ?
|
||||
[<span key="0">{text}</span>] :
|
||||
renderRelatedLocations(text, result.relatedLocations);
|
||||
renderRelatedLocations(text, result.relatedLocations, resultKey);
|
||||
|
||||
const currentResultExpanded = this.state.expanded[expansionIndex];
|
||||
const indicator = currentResultExpanded ? octicons.chevronDown : octicons.chevronRight;
|
||||
const location = result.locations !== undefined && result.locations.length > 0 &&
|
||||
renderSarifLocation(result.locations[0], Keys.none);
|
||||
renderSarifLocation(result.locations[0], resultKey);
|
||||
const locationCells = <td className="vscode-codeql__location-cell">{location}</td>;
|
||||
|
||||
const selectedItem = this.state.selectedItem;
|
||||
const resultRowIsSelected = selectedItem?.resultIndex === resultIndex && selectedItem.pathIndex === undefined;
|
||||
|
||||
if (result.codeFlows === undefined) {
|
||||
rows.push(
|
||||
<tr key={resultIndex} {...zebraStripe(resultIndex)}>
|
||||
<tr key={resultIndex} {...selectableZebraStripe(resultRowIsSelected, resultIndex)}>
|
||||
<td className="vscode-codeql__icon-cell">{octicons.info}</td>
|
||||
<td colSpan={3}>{msg}</td>
|
||||
{locationCells}
|
||||
@@ -225,7 +229,7 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
|
||||
[expansionIndex];
|
||||
|
||||
rows.push(
|
||||
<tr {...zebraStripe(resultIndex)} key={resultIndex}>
|
||||
<tr {...selectableZebraStripe(resultRowIsSelected, resultIndex)} key={resultIndex}>
|
||||
<td className="vscode-codeql__icon-cell vscode-codeql__dropdown-cell" onMouseDown={toggler(indices)}>
|
||||
{indicator}
|
||||
</td>
|
||||
@@ -268,7 +272,7 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
|
||||
const additionalMsg = step.location !== undefined ?
|
||||
renderSarifLocation(step.location, pathNodeKey) :
|
||||
'';
|
||||
const isSelected = Keys.equalsNotUndefined(this.state.selectedPathNode, pathNodeKey);
|
||||
const isSelected = Keys.equalsNotUndefined(this.state.selectedItem, pathNodeKey);
|
||||
const stepIndex = pathNodeIndex + 1; // Convert to 1-based
|
||||
const zebraIndex = resultIndex + stepIndex;
|
||||
rows.push(
|
||||
@@ -303,14 +307,33 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
|
||||
}
|
||||
|
||||
private handleNavigationEvent(event: NavigationEvent) {
|
||||
this.setState(prevState => {
|
||||
const { selectedPathNode } = prevState;
|
||||
if (selectedPathNode === undefined) return prevState;
|
||||
switch (event.t) {
|
||||
case 'navigatePath': {
|
||||
this.handleNavigatePathStepEvent(event);
|
||||
break;
|
||||
}
|
||||
case 'navigateAlert': {
|
||||
this.handleNavigateAlertEvent(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const path = Keys.getPath(this.props.resultSet.interpretation.data, selectedPathNode);
|
||||
private handleNavigatePathStepEvent(event: NavigatePathMsg) {
|
||||
this.setState(prevState => {
|
||||
const { selectedItem } = prevState;
|
||||
if (selectedItem === undefined) return prevState;
|
||||
|
||||
const selectedPath = selectedItem.pathIndex !== undefined
|
||||
? selectedItem
|
||||
: { ...selectedItem, pathIndex: 0 };
|
||||
|
||||
const path = Keys.getPath(this.props.resultSet.interpretation.data, selectedPath);
|
||||
if (path === undefined) return prevState;
|
||||
|
||||
const nextIndex = selectedPathNode.pathNodeIndex + event.direction;
|
||||
const nextIndex = selectedItem.pathNodeIndex !== undefined
|
||||
? (selectedItem.pathNodeIndex + event.direction)
|
||||
: 0;
|
||||
if (nextIndex < 0 || nextIndex >= path.locations.length) return prevState;
|
||||
|
||||
const sarifLoc = path.locations[nextIndex].location;
|
||||
@@ -324,8 +347,35 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
|
||||
}
|
||||
|
||||
jumpToLocation(loc, this.props.databaseUri);
|
||||
const newSelection = { ...selectedPathNode, pathNodeIndex: nextIndex };
|
||||
return { ...prevState, selectedPathNode: newSelection };
|
||||
const newSelection = { ...selectedPath, pathNodeIndex: nextIndex };
|
||||
return { ...prevState, selectedItem: newSelection };
|
||||
});
|
||||
}
|
||||
|
||||
private handleNavigateAlertEvent(event: NavigateAlertMsg) {
|
||||
this.setState(prevState => {
|
||||
const { selectedItem } = prevState;
|
||||
if (selectedItem === undefined) return prevState;
|
||||
|
||||
const nextIndex = selectedItem.resultIndex + event.direction;
|
||||
const nextSelection = { resultIndex: nextIndex };
|
||||
const result = Keys.getResult(this.props.resultSet.interpretation.data, nextSelection);
|
||||
if (result === undefined) {
|
||||
return prevState;
|
||||
}
|
||||
|
||||
const sarifLoc = result.locations?.[0];
|
||||
if (sarifLoc === undefined) {
|
||||
return prevState;
|
||||
}
|
||||
|
||||
const loc = parseSarifLocation(sarifLoc, this.props.resultSet.interpretation.sourceLocationPrefix);
|
||||
if (isNoLocation(loc)) {
|
||||
return prevState;
|
||||
}
|
||||
|
||||
jumpToLocation(loc, this.props.databaseUri);
|
||||
return { ...prevState, selectedItem: nextSelection };
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
SortedResultSetInfo,
|
||||
RawResultsSortState,
|
||||
NavigatePathMsg,
|
||||
NavigateAlertMsg,
|
||||
QueryMetadata,
|
||||
ResultsPaths,
|
||||
ALERTS_TABLE_NAME,
|
||||
@@ -62,7 +63,7 @@ interface ResultsViewState {
|
||||
isExpectingResultsUpdate: boolean;
|
||||
}
|
||||
|
||||
export type NavigationEvent = NavigatePathMsg;
|
||||
export type NavigationEvent = NavigatePathMsg | NavigateAlertMsg;
|
||||
|
||||
/**
|
||||
* Event handlers to be notified of navigation events coming from outside the webview.
|
||||
@@ -146,6 +147,7 @@ export class ResultsApp extends React.Component<Record<string, never>, ResultsVi
|
||||
});
|
||||
break;
|
||||
case 'navigatePath':
|
||||
case 'navigateAlert':
|
||||
onNavigation.fire(msg);
|
||||
break;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user