Skip to content

Commit 1017741

Browse files
authored
Merge pull request #492 from aeisenberg/aeisenberg/ast-viewer
Add the AST Viewer
2 parents b3dc7d7 + 0045891 commit 1017741

File tree

21 files changed

+1755
-84
lines changed

21 files changed

+1755
-84
lines changed

extensions/ql-vscode/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## [Unreleased]
44

5+
- Add the AST Viewer to inspect the QL AST of a source file in a database. Currently, only available for C/C++ sources.
56
- Fix error with choosing qlpack search path.
67
- Add the AST Viewer to inspect the QL AST of a source file in a database. Currently, only available for C/C++ sources.
78
- Fix pagination when there are no results.
Lines changed: 7 additions & 0 deletions
Loading
Lines changed: 7 additions & 0 deletions
Loading

extensions/ql-vscode/package.json

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,15 @@
2525
"onLanguage:ql",
2626
"onView:codeQLDatabases",
2727
"onView:codeQLQueryHistory",
28+
"onView:codeQLAstViewer",
2829
"onView:test-explorer",
2930
"onCommand:codeQL.checkForUpdatesToCLI",
3031
"onCommand:codeQLDatabases.chooseDatabaseFolder",
3132
"onCommand:codeQLDatabases.chooseDatabaseArchive",
3233
"onCommand:codeQLDatabases.chooseDatabaseInternet",
3334
"onCommand:codeQLDatabases.chooseDatabaseLgtm",
3435
"onCommand:codeQL.setCurrentDatabase",
36+
"onCommand:codeQL.viewAst",
3537
"onCommand:codeQL.chooseDatabaseFolder",
3638
"onCommand:codeQL.chooseDatabaseArchive",
3739
"onCommand:codeQL.chooseDatabaseInternet",
@@ -218,6 +220,10 @@
218220
"command": "codeQL.setCurrentDatabase",
219221
"title": "CodeQL: Set Current Database"
220222
},
223+
{
224+
"command": "codeQL.viewAst",
225+
"title": "CodeQL: View AST"
226+
},
221227
{
222228
"command": "codeQL.upgradeCurrentDatabase",
223229
"title": "CodeQL: Upgrade Current Database"
@@ -333,6 +339,18 @@
333339
{
334340
"command": "codeQLTests.acceptOutput",
335341
"title": "CodeQL: Accept Test Output"
342+
},
343+
{
344+
"command": "codeQLAstViewer.gotoCode",
345+
"title": "Go To Code"
346+
},
347+
{
348+
"command": "codeQLAstViewer.clear",
349+
"title": "Clear AST",
350+
"icon": {
351+
"light": "media/light/clear-all.svg",
352+
"dark": "media/dark/clear-all.svg"
353+
}
336354
}
337355
],
338356
"menus": {
@@ -366,6 +384,11 @@
366384
"command": "codeQLDatabases.chooseDatabaseLgtm",
367385
"when": "view == codeQLDatabases",
368386
"group": "navigation"
387+
},
388+
{
389+
"command": "codeQLAstViewer.clear",
390+
"when": "view == codeQLAstViewer && config.codeQL.experimentalAstViewer == true",
391+
"group": "navigation"
369392
}
370393
],
371394
"view/item/context": [
@@ -446,6 +469,11 @@
446469
"group": "9_qlCommands",
447470
"when": "resourceScheme == codeql-zip-archive || explorerResourceIsFolder || resourceExtname == .zip"
448471
},
472+
{
473+
"command": "codeQL.viewAst",
474+
"group": "9_qlCommands",
475+
"when": "resourceScheme == codeql-zip-archive && config.codeQL.experimentalAstViewer == true"
476+
},
449477
{
450478
"command": "codeQL.runQueries",
451479
"group": "9_qlCommands"
@@ -468,6 +496,10 @@
468496
"command": "codeQL.setCurrentDatabase",
469497
"when": "false"
470498
},
499+
{
500+
"command": "codeQL.viewAst",
501+
"when": "resourceScheme == codeql-zip-archive"
502+
},
471503
{
472504
"command": "codeQLDatabases.setCurrentDatabase",
473505
"when": "false"
@@ -543,6 +575,14 @@
543575
{
544576
"command": "codeQLQueryHistory.compareWith",
545577
"when": "false"
578+
},
579+
{
580+
"command": "codeQLAstViewer.gotoCode",
581+
"when": "false"
582+
},
583+
{
584+
"command": "codeQLAstViewer.clear",
585+
"when": "false"
546586
}
547587
],
548588
"editor/context": [
@@ -574,9 +614,25 @@
574614
{
575615
"id": "codeQLQueryHistory",
576616
"name": "Query History"
617+
},
618+
{
619+
"id": "codeQLAstViewer",
620+
"name": "AST Viewer",
621+
"when": "config.codeQL.experimentalAstViewer == true"
577622
}
578623
]
579-
}
624+
},
625+
"viewsWelcome": [
626+
{
627+
"view": "codeQLAstViewer",
628+
"contents": "Run the 'CodeQL: View AST' command on an open source file from a Code QL database.\n[View AST](command:codeQL.viewAst)",
629+
"when": "config.codeQL.experimentalAstViewer == true"
630+
},
631+
{
632+
"view": "codeQLQueryHistory",
633+
"contents": "Run the 'CodeQL: Run Query' command on QL query.\n[Run Query](command:codeQL.runQuery)"
634+
}
635+
]
580636
},
581637
"scripts": {
582638
"build": "gulp",
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import * as vscode from 'vscode';
2+
3+
import { DatabaseItem } from './databases';
4+
import { UrlValue, BqrsId } from './bqrs-cli-types';
5+
import fileRangeFromURI from './contextual/fileRangeFromURI';
6+
import { showLocation } from './interface-utils';
7+
8+
export interface AstItem {
9+
id: BqrsId;
10+
label?: string;
11+
location?: UrlValue;
12+
parent: AstItem | RootAstItem;
13+
children: AstItem[];
14+
order: number;
15+
}
16+
17+
export type RootAstItem = Omit<AstItem, 'parent'>;
18+
19+
class AstViewerDataProvider implements vscode.TreeDataProvider<AstItem | RootAstItem> {
20+
21+
public roots: RootAstItem[] = [];
22+
public db: DatabaseItem | undefined;
23+
24+
private _onDidChangeTreeData =
25+
new vscode.EventEmitter<AstItem | undefined>();
26+
readonly onDidChangeTreeData: vscode.Event<AstItem | undefined> =
27+
this._onDidChangeTreeData.event;
28+
29+
constructor() {
30+
vscode.commands.registerCommand('codeQLAstViewer.gotoCode',
31+
async (location: UrlValue, db: DatabaseItem) => {
32+
if (location) {
33+
await showLocation(fileRangeFromURI(location, db));
34+
}
35+
});
36+
}
37+
38+
refresh(): void {
39+
this._onDidChangeTreeData.fire();
40+
}
41+
getChildren(item?: AstItem): vscode.ProviderResult<(AstItem | RootAstItem)[]> {
42+
const children = item ? item.children : this.roots;
43+
return children.sort((c1, c2) => (c1.order - c2.order));
44+
}
45+
46+
getParent(item: AstItem): vscode.ProviderResult<AstItem> {
47+
return item.parent as AstItem;
48+
}
49+
50+
getTreeItem(item: AstItem): vscode.TreeItem {
51+
const line = typeof item.location === 'string'
52+
? item.location
53+
: item.location?.startLine;
54+
55+
const state = item.children.length
56+
? vscode.TreeItemCollapsibleState.Collapsed
57+
: vscode.TreeItemCollapsibleState.None;
58+
const treeItem = new vscode.TreeItem(item.label || '', state);
59+
treeItem.description = line ? `Line ${line}` : '';
60+
treeItem.id = String(item.id);
61+
treeItem.tooltip = `${treeItem.description} ${treeItem.label}`;
62+
treeItem.command = {
63+
command: 'codeQLAstViewer.gotoCode',
64+
title: 'Go To Code',
65+
tooltip: `Go To ${item.location}`,
66+
arguments: [item.location, this.db]
67+
};
68+
return treeItem;
69+
}
70+
}
71+
72+
export class AstViewer {
73+
private treeView: vscode.TreeView<AstItem | RootAstItem>;
74+
private treeDataProvider: AstViewerDataProvider;
75+
76+
constructor() {
77+
this.treeDataProvider = new AstViewerDataProvider();
78+
this.treeView = vscode.window.createTreeView('codeQLAstViewer', {
79+
treeDataProvider: this.treeDataProvider,
80+
showCollapseAll: true
81+
});
82+
83+
vscode.commands.registerCommand('codeQLAstViewer.clear', () => {
84+
this.clear();
85+
});
86+
}
87+
88+
updateRoots(roots: RootAstItem[], db: DatabaseItem, fileName: string) {
89+
this.treeDataProvider.roots = roots;
90+
this.treeDataProvider.db = db;
91+
this.treeDataProvider.refresh();
92+
this.treeView.message = `AST for ${fileName}`;
93+
this.treeView.reveal(roots[0], { focus: true });
94+
}
95+
96+
private clear() {
97+
this.treeDataProvider.roots = [];
98+
this.treeDataProvider.db = undefined;
99+
this.treeDataProvider.refresh();
100+
this.treeView.message = undefined;
101+
}
102+
}

extensions/ql-vscode/src/bqrs-cli-types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,12 @@ export interface BQRSInfo {
5353
'result-sets': ResultSetSchema[];
5454
}
5555

56+
export type BqrsId = number;
57+
5658
export interface EntityValue {
5759
url?: UrlValue;
5860
label?: string;
61+
id?: BqrsId;
5962
}
6063

6164
export interface LineColumnLocation {

extensions/ql-vscode/src/cli.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,18 @@ export interface TestCompleted {
9393
expected: string;
9494
}
9595

96+
/**
97+
* Optional arguments for the `bqrsDecode` function
98+
*/
99+
interface BqrsDecodeOptions {
100+
/** How many results to get. */
101+
pageSize?: number;
102+
/** The 0-based index of the first result to get. */
103+
offset?: number;
104+
/** The entity names to retrieve from the bqrs file. Default is url, string */
105+
entities?: string[];
106+
}
107+
96108
/**
97109
* This class manages a cli server started by `codeql execute cli-server` to
98110
* run commands without the overhead of starting a new java
@@ -494,12 +506,16 @@ export class CodeQLCliServer implements Disposable {
494506
* Gets the results from a bqrs.
495507
* @param bqrsPath The path to the bqrs.
496508
* @param resultSet The result set to get.
497-
* @param pageSize How many results to get.
498-
* @param offset The 0-based index of the first result to get.
509+
* @param options Optional BqrsDecodeOptions arguments
499510
*/
500-
async bqrsDecode(bqrsPath: string, resultSet: string, pageSize?: number, offset?: number): Promise<DecodedBqrsChunk> {
511+
async bqrsDecode(
512+
bqrsPath: string,
513+
resultSet: string,
514+
{ pageSize, offset, entities = ['url', 'string'] }: BqrsDecodeOptions = {}
515+
): Promise<DecodedBqrsChunk> {
516+
501517
const subcommandArgs = [
502-
'--entities=url,string',
518+
`--entities=${entities.join(',')}`,
503519
'--result-set', resultSet,
504520
].concat(
505521
pageSize ? ['--rows', pageSize.toString()] : []

extensions/ql-vscode/src/config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,15 @@ export class QueryHistoryConfigListener extends ConfigListener implements QueryH
209209
return QUERY_HISTORY_FORMAT_SETTING.getValue<string>();
210210
}
211211
}
212+
213+
// Enable experimental features
214+
215+
/**
216+
* Any settings below are deliberately not in package.json so that
217+
* they do not appear in the settings ui in vscode itself. If users
218+
* want to enable experimental features, they can add them directly in
219+
* their vscode settings json file.
220+
*/
221+
222+
/* Advanced setting: used to enable the AST Viewer. */
223+
export const EXPERIMENTAL_AST_VIEWER = new Setting('experimentalAstViewer', ROOT_SETTING);

0 commit comments

Comments
 (0)