Add QueryDiscovery class
This commit is contained in:
@@ -733,7 +733,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.queryDiscovery = new QueryDiscovery(app, cliServer);
|
||||
this.push(this.queryDiscovery);
|
||||
this.queryDiscovery.refresh();
|
||||
|
||||
this.queriesPanel = new QueriesPanel();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
137
extensions/ql-vscode/src/queries-panel/query-discovery.ts
Normal file
137
extensions/ql-vscode/src/queries-panel/query-discovery.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { dirname, basename, normalize, relative } from "path";
|
||||
import { Discovery } from "../common/discovery";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { pathExists } from "fs-extra";
|
||||
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";
|
||||
|
||||
/**
|
||||
* 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(
|
||||
private readonly 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 = this.app.workspaceFolders;
|
||||
if (workspaceFolders === undefined || 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);
|
||||
|
||||
// Don't try discovery on workspace folders that don't exist on the filesystem
|
||||
if (await pathExists(fullPath)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user