Skip to content

Commit 5cbb7b4

Browse files
Update QueryDiscovery to use FilePathDiscovery and QueryPackDiscovery
1 parent a9d59ae commit 5cbb7b4

5 files changed

Lines changed: 276 additions & 312 deletions

File tree

extensions/ql-vscode/src/queries-panel/queries-module.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { isCanary, showQueriesPanel } from "../config";
55
import { DisposableObject } from "../pure/disposable-object";
66
import { QueriesPanel } from "./queries-panel";
77
import { QueryDiscovery } from "./query-discovery";
8+
import { QueryPackDiscovery } from "./query-pack-discovery";
89

910
export class QueriesModule extends DisposableObject {
1011
private constructor(readonly app: App) {
@@ -19,9 +20,16 @@ export class QueriesModule extends DisposableObject {
1920
}
2021
void extLogger.log("Initializing queries panel.");
2122

22-
const queryDiscovery = new QueryDiscovery(app.environment, cliServer);
23+
const queryPackDiscovery = new QueryPackDiscovery(cliServer);
24+
this.push(queryPackDiscovery);
25+
void queryPackDiscovery.initialRefresh();
26+
27+
const queryDiscovery = new QueryDiscovery(
28+
app.environment,
29+
queryPackDiscovery,
30+
);
2331
this.push(queryDiscovery);
24-
void queryDiscovery.refresh();
32+
void queryDiscovery.initialRefresh();
2533

2634
const queriesPanel = new QueriesPanel(queryDiscovery);
2735
this.push(queriesPanel);
Lines changed: 84 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,136 +1,116 @@
11
import { dirname, basename, normalize, relative } from "path";
2-
import { Discovery } from "../common/discovery";
3-
import { CodeQLCliServer } from "../codeql-cli/cli";
4-
import {
5-
Event,
6-
EventEmitter,
7-
RelativePattern,
8-
Uri,
9-
WorkspaceFolder,
10-
workspace,
11-
} from "vscode";
12-
import { MultiFileSystemWatcher } from "../common/vscode/multi-file-system-watcher";
2+
import { Event } from "vscode";
133
import { EnvironmentContext } from "../common/app";
14-
import { FileTreeDirectory, FileTreeLeaf } from "../common/file-tree-nodes";
15-
import { getOnDiskWorkspaceFoldersObjects } from "../common/vscode/workspace-folders";
16-
import { AppEventEmitter } from "../common/events";
4+
import {
5+
FileTreeDirectory,
6+
FileTreeLeaf,
7+
FileTreeNode,
8+
} from "../common/file-tree-nodes";
179
import { QueryDiscoverer } from "./query-tree-data-provider";
18-
import { extLogger } from "../common";
10+
import { FilePathDiscovery } from "../common/vscode/file-path-discovery";
11+
import { containsPath } from "../pure/files";
12+
import { getOnDiskWorkspaceFoldersObjects } from "../common/vscode/workspace-folders";
1913

20-
/**
21-
* The results of discovering queries.
22-
*/
23-
export interface QueryDiscoveryResults {
24-
/**
25-
* A tree of directories and query files.
26-
* May have multiple roots because of multiple workspaces.
27-
*/
28-
queries: Array<FileTreeDirectory<string>>;
14+
const QUERY_FILE_EXTENSION = ".ql";
2915

30-
/**
31-
* File system paths to watch. If any ql file changes in these directories
32-
* or any subdirectories, then this could signify a change in queries.
33-
*/
34-
watchPaths: Uri[];
16+
export interface QueryPackDiscoverer {
17+
getLanguageForQueryFile(queryPath: string): string | undefined;
18+
onDidChangeQueryPacks: Event<void>;
19+
}
20+
21+
interface Query {
22+
path: string;
23+
language: string | undefined;
3524
}
3625

3726
/**
38-
* Discovers all query files contained in the QL packs in a given workspace folder.
27+
* Discovers all query files in the workspace.
3928
*/
40-
export class QueryDiscovery extends Discovery implements QueryDiscoverer {
41-
private results: Array<FileTreeDirectory<string>> | undefined;
42-
43-
private readonly onDidChangeQueriesEmitter: AppEventEmitter<void>;
44-
private readonly watcher: MultiFileSystemWatcher = this.push(
45-
new MultiFileSystemWatcher(),
46-
);
47-
29+
export class QueryDiscovery
30+
extends FilePathDiscovery<Query>
31+
implements QueryDiscoverer
32+
{
4833
constructor(
4934
private readonly env: EnvironmentContext,
50-
private readonly cliServer: CodeQLCliServer,
35+
private readonly queryPackDiscovery: QueryPackDiscoverer,
5136
) {
52-
super("Query Discovery", extLogger);
53-
54-
this.onDidChangeQueriesEmitter = this.push(new EventEmitter<void>());
55-
this.push(workspace.onDidChangeWorkspaceFolders(this.refresh.bind(this)));
56-
this.push(this.watcher.onDidChange(this.refresh.bind(this)));
57-
}
37+
super("Query Discovery", `**/*${QUERY_FILE_EXTENSION}`);
5838

59-
public get queries(): Array<FileTreeDirectory<string>> | undefined {
60-
return this.results;
39+
this.push(
40+
this.queryPackDiscovery.onDidChangeQueryPacks(
41+
this.recomputeAllQueryLanguages.bind(this),
42+
),
43+
);
6144
}
6245

6346
/**
64-
* Event to be fired when the set of discovered queries may have changed.
47+
* Event that fires when the set of queries in the workspace changes.
6548
*/
6649
public get onDidChangeQueries(): Event<void> {
67-
return this.onDidChangeQueriesEmitter.event;
68-
}
69-
70-
protected async discover() {
71-
const workspaceFolders = getOnDiskWorkspaceFoldersObjects();
72-
73-
this.results = await this.discoverQueries(workspaceFolders);
74-
75-
this.watcher.clear();
76-
for (const watchPath of workspaceFolders.map((f) => f.uri)) {
77-
// Watch for changes to any `.ql` file
78-
this.watcher.addWatch(new RelativePattern(watchPath, "**/*.{ql}"));
79-
// need to explicitly watch for changes to directories themselves.
80-
this.watcher.addWatch(new RelativePattern(watchPath, "**/"));
81-
}
82-
this.onDidChangeQueriesEmitter.fire();
50+
return this.onDidChangePathsEmitter.event;
8351
}
8452

8553
/**
86-
* Discover all queries in the specified directory and its subdirectories.
87-
* @returns A `QueryDirectory` object describing the contents of the directory, or `undefined` if
88-
* no queries were found.
54+
* Return all known queries, represented as a tree.
55+
*
56+
* Trivial directories where there is only one child will be collapsed into a single node.
8957
*/
90-
private async discoverQueries(
91-
workspaceFolders: readonly WorkspaceFolder[],
92-
): Promise<Array<FileTreeDirectory<string>>> {
93-
const rootDirectories = [];
94-
for (const workspaceFolder of workspaceFolders) {
95-
const root = await this.discoverQueriesInWorkspace(workspaceFolder);
96-
if (root !== undefined) {
97-
rootDirectories.push(root);
58+
public buildQueryTree(): Array<FileTreeNode<string>> {
59+
const roots = [];
60+
for (const workspaceFolder of getOnDiskWorkspaceFoldersObjects()) {
61+
const queriesInRoot = this.paths.filter((query) =>
62+
containsPath(workspaceFolder.uri.fsPath, query.path),
63+
);
64+
if (queriesInRoot.length > 0) {
65+
const root = new FileTreeDirectory<string>(
66+
workspaceFolder.uri.fsPath,
67+
workspaceFolder.name,
68+
this.env,
69+
);
70+
for (const query of queriesInRoot) {
71+
const dirName = dirname(normalize(relative(root.path, query.path)));
72+
const parentDirectory = root.createDirectory(dirName);
73+
parentDirectory.addChild(
74+
new FileTreeLeaf<string>(
75+
query.path,
76+
basename(query.path),
77+
query.language,
78+
),
79+
);
80+
}
81+
root.finish();
82+
roots.push(root);
9883
}
9984
}
100-
return rootDirectories;
85+
return roots;
86+
}
87+
88+
protected async getDataForPath(path: string): Promise<Query> {
89+
const language = this.determineQueryLanguage(path);
90+
return { path, language };
10191
}
10292

103-
private async discoverQueriesInWorkspace(
104-
workspaceFolder: WorkspaceFolder,
105-
): Promise<FileTreeDirectory<string> | undefined> {
106-
const fullPath = workspaceFolder.uri.fsPath;
107-
const name = workspaceFolder.name;
93+
protected pathIsRelevant(path: string): boolean {
94+
return path.endsWith(QUERY_FILE_EXTENSION);
95+
}
10896

109-
// We don't want to log each invocation of resolveQueries, since it clutters up the log.
110-
const silent = true;
111-
const resolvedQueries = await this.cliServer.resolveQueries(
112-
fullPath,
113-
silent,
114-
);
115-
if (resolvedQueries.length === 0) {
116-
return undefined;
117-
}
97+
protected shouldOverwriteExistingData(
98+
newData: Query,
99+
existingData: Query,
100+
): boolean {
101+
return newData.language !== existingData.language;
102+
}
118103

119-
const rootDirectory = new FileTreeDirectory<string>(
120-
fullPath,
121-
name,
122-
this.env,
123-
);
124-
for (const queryPath of resolvedQueries) {
125-
const relativePath = normalize(relative(fullPath, queryPath));
126-
const dirName = dirname(relativePath);
127-
const parentDirectory = rootDirectory.createDirectory(dirName);
128-
parentDirectory.addChild(
129-
new FileTreeLeaf<string>(queryPath, basename(queryPath), "language"),
130-
);
104+
private recomputeAllQueryLanguages() {
105+
// All we know is that something has changed in the set of known query packs.
106+
// We have no choice but to recompute the language for all queries.
107+
for (const query of this.paths) {
108+
query.language = this.determineQueryLanguage(query.path);
131109
}
110+
this.onDidChangePathsEmitter.fire();
111+
}
132112

133-
rootDirectory.finish();
134-
return rootDirectory;
113+
private determineQueryLanguage(path: string): string | undefined {
114+
return this.queryPackDiscovery.getLanguageForQueryFile(path);
135115
}
136116
}

extensions/ql-vscode/src/queries-panel/query-tree-data-provider.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { DisposableObject } from "../pure/disposable-object";
44
import { FileTreeNode } from "../common/file-tree-nodes";
55

66
export interface QueryDiscoverer {
7-
readonly queries: Array<FileTreeNode<string>> | undefined;
7+
readonly buildQueryTree: () => Array<FileTreeNode<string>>;
88
readonly onDidChangeQueries: Event<void>;
99
}
1010

@@ -34,9 +34,9 @@ export class QueryTreeDataProvider
3434
}
3535

3636
private createTree(): QueryTreeViewItem[] {
37-
return (this.queryDiscoverer.queries || []).map(
38-
this.convertFileTreeNode.bind(this),
39-
);
37+
return this.queryDiscoverer
38+
.buildQueryTree()
39+
.map(this.convertFileTreeNode.bind(this));
4040
}
4141

4242
private convertFileTreeNode(

0 commit comments

Comments
 (0)