Merge pull request #2433 from github/robertbrignull/query-discovery
Hook queries panel up to real data
This commit is contained in:
@@ -134,6 +134,11 @@ export interface SourceInfo {
|
||||
sourceLocationPrefix: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The expected output of `codeql resolve queries`.
|
||||
*/
|
||||
export type ResolvedQueries = string[];
|
||||
|
||||
/**
|
||||
* The expected output of `codeql resolve tests`.
|
||||
*/
|
||||
@@ -731,6 +736,20 @@ export class CodeQLCliServer implements Disposable {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all available queries in a given directory.
|
||||
* @param queryDir Root of directory tree to search for queries.
|
||||
* @returns The list of queries that were found.
|
||||
*/
|
||||
public async resolveQueries(queryDir: string): Promise<ResolvedQueries> {
|
||||
const subcommandArgs = [queryDir];
|
||||
return await this.runJsonCodeQlCliCommand<ResolvedQueries>(
|
||||
["resolve", "queries"],
|
||||
subcommandArgs,
|
||||
"Resolving queries",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all available QL tests in a given directory.
|
||||
* @param testPath Root of directory tree to search for tests.
|
||||
|
||||
@@ -755,7 +755,7 @@ async function activateWithInstalledDistribution(
|
||||
);
|
||||
ctx.subscriptions.push(databaseUI);
|
||||
|
||||
QueriesModule.initialize(app);
|
||||
QueriesModule.initialize(app, cliServer);
|
||||
|
||||
void extLogger.log("Initializing evaluator log viewer.");
|
||||
const evalLogViewer = new EvalLogViewer();
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { extLogger } from "../common";
|
||||
import { App, AppMode } from "../common/app";
|
||||
import { isCanary, showQueriesPanel } from "../config";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { QueriesPanel } from "./queries-panel";
|
||||
import { QueryDiscovery } from "./query-discovery";
|
||||
|
||||
export class QueriesModule extends DisposableObject {
|
||||
private queriesPanel: QueriesPanel | undefined;
|
||||
private queryDiscovery: QueryDiscovery | undefined;
|
||||
|
||||
private constructor(readonly app: App) {
|
||||
super();
|
||||
}
|
||||
|
||||
private initialize(app: App): void {
|
||||
private initialize(app: App, cliServer: CodeQLCliServer): void {
|
||||
if (app.mode === AppMode.Production || !isCanary() || !showQueriesPanel()) {
|
||||
// Currently, we only want to expose the new panel when we are in development and canary mode
|
||||
// and the developer has enabled the "Show queries panel" flag.
|
||||
@@ -19,15 +22,22 @@ export class QueriesModule extends DisposableObject {
|
||||
}
|
||||
void extLogger.log("Initializing queries panel.");
|
||||
|
||||
this.queriesPanel = new QueriesPanel();
|
||||
this.queryDiscovery = new QueryDiscovery(app, cliServer);
|
||||
this.push(this.queryDiscovery);
|
||||
this.queryDiscovery.refresh();
|
||||
|
||||
this.queriesPanel = new QueriesPanel(this.queryDiscovery);
|
||||
this.push(this.queriesPanel);
|
||||
}
|
||||
|
||||
public static initialize(app: App): QueriesModule {
|
||||
public static initialize(
|
||||
app: App,
|
||||
cliServer: CodeQLCliServer,
|
||||
): QueriesModule {
|
||||
const queriesModule = new QueriesModule(app);
|
||||
app.subscriptions.push(queriesModule);
|
||||
|
||||
queriesModule.initialize(app);
|
||||
queriesModule.initialize(app, cliServer);
|
||||
return queriesModule;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,16 @@ import * as vscode from "vscode";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { QueryTreeDataProvider } from "./query-tree-data-provider";
|
||||
import { QueryTreeViewItem } from "./query-tree-view-item";
|
||||
import { QueryDiscovery } from "./query-discovery";
|
||||
|
||||
export class QueriesPanel extends DisposableObject {
|
||||
private readonly dataProvider: QueryTreeDataProvider;
|
||||
private readonly treeView: vscode.TreeView<QueryTreeViewItem>;
|
||||
|
||||
public constructor() {
|
||||
public constructor(queryDiscovery: QueryDiscovery) {
|
||||
super();
|
||||
|
||||
this.dataProvider = new QueryTreeDataProvider();
|
||||
this.dataProvider = new QueryTreeDataProvider(queryDiscovery);
|
||||
|
||||
this.treeView = vscode.window.createTreeView("codeQLQueries", {
|
||||
treeDataProvider: this.dataProvider,
|
||||
|
||||
132
extensions/ql-vscode/src/queries-panel/query-discovery.ts
Normal file
132
extensions/ql-vscode/src/queries-panel/query-discovery.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { dirname, basename, normalize, relative } from "path";
|
||||
import { Discovery } from "../common/discovery";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import {
|
||||
Event,
|
||||
EventEmitter,
|
||||
RelativePattern,
|
||||
Uri,
|
||||
WorkspaceFolder,
|
||||
} from "vscode";
|
||||
import { MultiFileSystemWatcher } from "../common/vscode/multi-file-system-watcher";
|
||||
import { App } from "../common/app";
|
||||
import { FileTreeDirectory, FileTreeLeaf } from "../common/file-tree-nodes";
|
||||
import { getOnDiskWorkspaceFoldersObjects } from "../helpers";
|
||||
|
||||
/**
|
||||
* The results of discovering queries.
|
||||
*/
|
||||
interface QueryDiscoveryResults {
|
||||
/**
|
||||
* A tree of directories and query files.
|
||||
* May have multiple roots because of multiple workspaces.
|
||||
*/
|
||||
queries: FileTreeDirectory[];
|
||||
|
||||
/**
|
||||
* File system paths to watch. If any ql file changes in these directories
|
||||
* or any subdirectories, then this could signify a change in queries.
|
||||
*/
|
||||
watchPaths: Uri[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovers all query files contained in the QL packs in a given workspace folder.
|
||||
*/
|
||||
export class QueryDiscovery extends Discovery<QueryDiscoveryResults> {
|
||||
private results: QueryDiscoveryResults | undefined;
|
||||
|
||||
private readonly onDidChangeQueriesEmitter = this.push(
|
||||
new EventEmitter<void>(),
|
||||
);
|
||||
private readonly watcher: MultiFileSystemWatcher = this.push(
|
||||
new MultiFileSystemWatcher(),
|
||||
);
|
||||
|
||||
constructor(app: App, private readonly cliServer: CodeQLCliServer) {
|
||||
super("Query Discovery");
|
||||
|
||||
this.push(app.onDidChangeWorkspaceFolders(this.refresh.bind(this)));
|
||||
this.push(this.watcher.onDidChange(this.refresh.bind(this)));
|
||||
}
|
||||
|
||||
public get queries(): FileTreeDirectory[] | undefined {
|
||||
return this.results?.queries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event to be fired when the set of discovered queries may have changed.
|
||||
*/
|
||||
public get onDidChangeQueries(): Event<void> {
|
||||
return this.onDidChangeQueriesEmitter.event;
|
||||
}
|
||||
|
||||
protected async discover(): Promise<QueryDiscoveryResults> {
|
||||
const workspaceFolders = getOnDiskWorkspaceFoldersObjects();
|
||||
if (workspaceFolders.length === 0) {
|
||||
return {
|
||||
queries: [],
|
||||
watchPaths: [],
|
||||
};
|
||||
}
|
||||
|
||||
const queries = await this.discoverQueries(workspaceFolders);
|
||||
|
||||
return {
|
||||
queries,
|
||||
watchPaths: workspaceFolders.map((f) => f.uri),
|
||||
};
|
||||
}
|
||||
|
||||
protected update(results: QueryDiscoveryResults): void {
|
||||
this.results = results;
|
||||
|
||||
this.watcher.clear();
|
||||
for (const watchPath of results.watchPaths) {
|
||||
// Watch for changes to any `.ql` file
|
||||
this.watcher.addWatch(new RelativePattern(watchPath, "**/*.{ql}"));
|
||||
// need to explicitly watch for changes to directories themselves.
|
||||
this.watcher.addWatch(new RelativePattern(watchPath, "**/"));
|
||||
}
|
||||
this.onDidChangeQueriesEmitter.fire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover all queries in the specified directory and its subdirectories.
|
||||
* @returns A `QueryDirectory` object describing the contents of the directory, or `undefined` if
|
||||
* no queries were found.
|
||||
*/
|
||||
private async discoverQueries(
|
||||
workspaceFolders: readonly WorkspaceFolder[],
|
||||
): Promise<FileTreeDirectory[]> {
|
||||
const rootDirectories = [];
|
||||
for (const workspaceFolder of workspaceFolders) {
|
||||
rootDirectories.push(
|
||||
await this.discoverQueriesInWorkspace(workspaceFolder),
|
||||
);
|
||||
}
|
||||
return rootDirectories;
|
||||
}
|
||||
|
||||
private async discoverQueriesInWorkspace(
|
||||
workspaceFolder: WorkspaceFolder,
|
||||
): Promise<FileTreeDirectory> {
|
||||
const fullPath = workspaceFolder.uri.fsPath;
|
||||
const name = workspaceFolder.name;
|
||||
|
||||
const rootDirectory = new FileTreeDirectory(fullPath, name);
|
||||
|
||||
const resolvedQueries = await this.cliServer.resolveQueries(fullPath);
|
||||
for (const queryPath of resolvedQueries) {
|
||||
const relativePath = normalize(relative(fullPath, queryPath));
|
||||
const dirName = dirname(relativePath);
|
||||
const parentDirectory = rootDirectory.createDirectory(dirName);
|
||||
parentDirectory.addChild(
|
||||
new FileTreeLeaf(queryPath, basename(queryPath)),
|
||||
);
|
||||
}
|
||||
|
||||
rootDirectory.finish();
|
||||
return rootDirectory;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,48 @@
|
||||
import * as vscode from "vscode";
|
||||
import { Event, EventEmitter, TreeDataProvider, TreeItem } from "vscode";
|
||||
import { QueryTreeViewItem } from "./query-tree-view-item";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { QueryDiscovery } from "./query-discovery";
|
||||
import { FileTreeNode } from "../common/file-tree-nodes";
|
||||
|
||||
export class QueryTreeDataProvider
|
||||
extends DisposableObject
|
||||
implements vscode.TreeDataProvider<QueryTreeViewItem>
|
||||
implements TreeDataProvider<QueryTreeViewItem>
|
||||
{
|
||||
private queryTreeItems: QueryTreeViewItem[];
|
||||
|
||||
public constructor() {
|
||||
private readonly onDidChangeTreeDataEmitter = this.push(
|
||||
new EventEmitter<void>(),
|
||||
);
|
||||
|
||||
public constructor(private readonly queryDiscovery: QueryDiscovery) {
|
||||
super();
|
||||
|
||||
queryDiscovery.onDidChangeQueries(() => {
|
||||
this.queryTreeItems = this.createTree();
|
||||
this.onDidChangeTreeDataEmitter.fire();
|
||||
});
|
||||
|
||||
this.queryTreeItems = this.createTree();
|
||||
}
|
||||
|
||||
public get onDidChangeTreeData(): Event<void> {
|
||||
return this.onDidChangeTreeDataEmitter.event;
|
||||
}
|
||||
|
||||
private createTree(): QueryTreeViewItem[] {
|
||||
// Temporary mock data, just to populate the tree view.
|
||||
return [
|
||||
new QueryTreeViewItem("custom-pack", [
|
||||
new QueryTreeViewItem("custom-pack/example.ql", []),
|
||||
]),
|
||||
new QueryTreeViewItem("ql", [
|
||||
new QueryTreeViewItem("ql/javascript", [
|
||||
new QueryTreeViewItem("ql/javascript/example.ql", []),
|
||||
]),
|
||||
new QueryTreeViewItem("ql/go", [
|
||||
new QueryTreeViewItem("ql/go/security", [
|
||||
new QueryTreeViewItem("ql/go/security/query1.ql", []),
|
||||
new QueryTreeViewItem("ql/go/security/query2.ql", []),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
];
|
||||
return (this.queryDiscovery.queries || []).map(
|
||||
this.convertFileTreeNode.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
private convertFileTreeNode(
|
||||
fileTreeDirectory: FileTreeNode,
|
||||
): QueryTreeViewItem {
|
||||
return new QueryTreeViewItem(
|
||||
fileTreeDirectory.name,
|
||||
fileTreeDirectory.path,
|
||||
fileTreeDirectory.children.map(this.convertFileTreeNode.bind(this)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,7 +50,7 @@ export class QueryTreeDataProvider
|
||||
* @param item The item to represent.
|
||||
* @returns The UI presentation of the item.
|
||||
*/
|
||||
public getTreeItem(item: QueryTreeViewItem): vscode.TreeItem {
|
||||
public getTreeItem(item: QueryTreeViewItem): TreeItem {
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import * as vscode from "vscode";
|
||||
import { basename } from "path";
|
||||
|
||||
export class QueryTreeViewItem extends vscode.TreeItem {
|
||||
constructor(path: string, public readonly children: QueryTreeViewItem[]) {
|
||||
super(basename(path));
|
||||
constructor(
|
||||
name: string,
|
||||
path: string,
|
||||
public readonly children: QueryTreeViewItem[],
|
||||
) {
|
||||
super(name);
|
||||
this.tooltip = path;
|
||||
this.collapsibleState = this.children.length
|
||||
? vscode.TreeItemCollapsibleState.Collapsed
|
||||
|
||||
Reference in New Issue
Block a user