Skip to content

Commit e056c61

Browse files
authored
Merge pull request #175 from asgerf/asgerf/path-result-nav
Add commands for navigating the steps on a path
2 parents 0d7eb93 + 8c3bd77 commit e056c61

File tree

9 files changed

+316
-100
lines changed

9 files changed

+316
-100
lines changed

extensions/ql-vscode/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,14 @@
170170
{
171171
"command": "codeQLQueryHistory.itemClicked",
172172
"title": "Query History Item"
173+
},
174+
{
175+
"command": "codeQLQueryResults.nextPathStep",
176+
"title": "CodeQL: Show Next Step on Path"
177+
},
178+
{
179+
"command": "codeQLQueryResults.previousPathStep",
180+
"title": "CodeQL: Show Previous Step on Path"
173181
}
174182
],
175183
"menus": {

extensions/ql-vscode/src/interface-types.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,15 @@ export interface SetStateMsg {
6565
shouldKeepOldResultsWhileRendering: boolean;
6666
};
6767

68-
export type IntoResultsViewMsg = ResultsUpdatingMsg | SetStateMsg;
68+
/** Advance to the next or previous path no in the path viewer */
69+
export interface NavigatePathMsg {
70+
t: 'navigatePath',
71+
72+
/** 1 for next, -1 for previous */
73+
direction: number;
74+
}
75+
76+
export type IntoResultsViewMsg = ResultsUpdatingMsg | SetStateMsg | NavigatePathMsg;
6977

7078
export type FromResultsViewMsg = ViewSourceFileMsg | ToggleDiagnostics | ChangeSortMsg | ResultViewLoaded;
7179

extensions/ql-vscode/src/interface.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ export class InterfaceManager extends DisposableObject {
9999
super();
100100
this.push(this._diagnosticCollection);
101101
this.push(vscode.window.onDidChangeTextEditorSelection(this.handleSelectionChange.bind(this)));
102+
this.push(vscode.commands.registerCommand('codeQLQueryResults.nextPathStep', this.navigatePathStep.bind(this, 1)));
103+
this.push(vscode.commands.registerCommand('codeQLQueryResults.previousPathStep', this.navigatePathStep.bind(this, -1)));
104+
}
105+
106+
navigatePathStep(direction: number) {
107+
this.postMessage({ t: "navigatePath", direction });
102108
}
103109

104110
// Returns the webview panel, creating it if it doesn't already
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import * as sarif from 'sarif';
2+
3+
/**
4+
* Identifies one of the results in a result set by its index in the result list.
5+
*/
6+
export interface Result {
7+
resultIndex: number;
8+
}
9+
10+
/**
11+
* Identifies one of the paths associated with a result.
12+
*/
13+
export interface Path extends Result {
14+
pathIndex: number;
15+
}
16+
17+
/**
18+
* Identifies one of the nodes in a path.
19+
*/
20+
export interface PathNode extends Path {
21+
pathNodeIndex: number;
22+
}
23+
24+
/** Alias for `undefined` but more readable in some cases */
25+
export const none: PathNode | undefined = undefined;
26+
27+
/**
28+
* Looks up a specific result in a result set.
29+
*/
30+
export function getResult(sarif: sarif.Log, key: Result): sarif.Result | undefined {
31+
if (sarif.runs.length === 0) return undefined;
32+
if (sarif.runs[0].results === undefined) return undefined;
33+
const results = sarif.runs[0].results;
34+
return results[key.resultIndex];
35+
}
36+
37+
/**
38+
* Looks up a specific path in a result set.
39+
*/
40+
export function getPath(sarif: sarif.Log, key: Path): sarif.ThreadFlow | undefined {
41+
let result = getResult(sarif, key);
42+
if (result === undefined) return undefined;
43+
let index = -1;
44+
if (result.codeFlows === undefined) return undefined;
45+
for (let codeFlows of result.codeFlows) {
46+
for (let threadFlow of codeFlows.threadFlows) {
47+
++index;
48+
if (index == key.pathIndex)
49+
return threadFlow;
50+
}
51+
}
52+
return undefined;
53+
}
54+
55+
/**
56+
* Looks up a specific path node in a result set.
57+
*/
58+
export function getPathNode(sarif: sarif.Log, key: PathNode): sarif.Location | undefined {
59+
let path = getPath(sarif, key);
60+
if (path === undefined) return undefined;
61+
return path.locations[key.pathNodeIndex];
62+
}
63+
64+
/**
65+
* Returns true if the two keys are both `undefined` or contain the same set of indices.
66+
*/
67+
export function equals(key1: PathNode | undefined, key2: PathNode | undefined): boolean {
68+
if (key1 === key2) return true;
69+
if (key1 === undefined || key2 === undefined) return false;
70+
return key1.resultIndex === key2.resultIndex && key1.pathIndex === key2.pathIndex && key1.pathNodeIndex === key2.pathNodeIndex;
71+
}
72+
73+
/**
74+
* Returns true if the two keys contain the same set of indices and neither are `undefined`.
75+
*/
76+
export function equalsNotUndefined(key1: PathNode | undefined, key2: PathNode | undefined): boolean {
77+
if (key1 === undefined || key2 === undefined) return false;
78+
return key1.resultIndex === key2.resultIndex && key1.pathIndex === key2.pathIndex && key1.pathNodeIndex === key2.pathNodeIndex;
79+
}
80+
81+
/**
82+
* Returns the list of paths in the given SARIF result.
83+
*
84+
* Path nodes indices are relative to this flattened list.
85+
*/
86+
export function getAllPaths(result: sarif.Result): sarif.ThreadFlow[] {
87+
if (result.codeFlows === undefined) return [];
88+
let paths = [];
89+
for (const codeFlow of result.codeFlows) {
90+
for (const threadFlow of codeFlow.threadFlows) {
91+
paths.push(threadFlow);
92+
}
93+
}
94+
return paths;
95+
}

0 commit comments

Comments
 (0)