Skip to content

Commit 0a0b9e5

Browse files
Merge pull request #2523 from github/robertbrignull/queries-loading
Display different message when queries are loading vs no queries found
2 parents 6acfb8d + c876867 commit 0a0b9e5

11 files changed

Lines changed: 171 additions & 60 deletions

File tree

extensions/ql-vscode/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1699,7 +1699,7 @@
16991699
},
17001700
{
17011701
"view": "codeQLQueries",
1702-
"contents": "This workspace doesn't contain any CodeQL queries at the moment."
1702+
"contents": "Looking for queries..."
17031703
},
17041704
{
17051705
"view": "codeQLDatabases",

extensions/ql-vscode/src/common/vscode/file-path-discovery.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ interface PathData {
3434
* relevant, and what extra data to compute for each file.
3535
*/
3636
export abstract class FilePathDiscovery<T extends PathData> extends Discovery {
37+
/**
38+
* Has `discover` been called. This allows distinguishing between
39+
* "no paths found" and not having scanned yet.
40+
*/
41+
private discoverHasCompletedOnce = false;
42+
3743
/** The set of known paths and associated data that we are tracking */
3844
private pathData: T[] = [];
3945

@@ -73,7 +79,10 @@ export abstract class FilePathDiscovery<T extends PathData> extends Discovery {
7379
this.push(this.watcher.onDidChange(this.fileChanged.bind(this)));
7480
}
7581

76-
protected getPathData(): ReadonlyArray<Readonly<T>> {
82+
protected getPathData(): ReadonlyArray<Readonly<T>> | undefined {
83+
if (!this.discoverHasCompletedOnce) {
84+
return undefined;
85+
}
7786
return this.pathData;
7887
}
7988

@@ -118,7 +127,8 @@ export abstract class FilePathDiscovery<T extends PathData> extends Discovery {
118127
});
119128

120129
this.updateWatchers();
121-
return this.refresh();
130+
await this.refresh();
131+
this.onDidChangePathDataEmitter.fire();
122132
}
123133

124134
private workspaceFoldersChanged(event: WorkspaceFoldersChangeEvent) {
@@ -171,6 +181,7 @@ export abstract class FilePathDiscovery<T extends PathData> extends Discovery {
171181
}
172182
}
173183

184+
this.discoverHasCompletedOnce = true;
174185
if (pathsUpdated) {
175186
this.onDidChangePathDataEmitter.fire();
176187
}

extensions/ql-vscode/src/local-queries/local-queries.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,15 +132,20 @@ export class LocalQueries extends DisposableObject {
132132
private async runQueryFromQueriesPanel(
133133
queryTreeViewItem: QueryTreeViewItem,
134134
): Promise<void> {
135-
await this.runQuery(Uri.file(queryTreeViewItem.path));
135+
if (queryTreeViewItem.path !== undefined) {
136+
await this.runQuery(Uri.file(queryTreeViewItem.path));
137+
}
136138
}
137139

138140
private async runQueriesFromQueriesPanel(
139141
queryTreeViewItem: QueryTreeViewItem,
140142
): Promise<void> {
141-
const uris = queryTreeViewItem.children.map((child) =>
142-
Uri.file(child.path),
143-
);
143+
const uris = [];
144+
for (const child of queryTreeViewItem.children) {
145+
if (child.path !== undefined) {
146+
uris.push(Uri.file(child.path));
147+
}
148+
}
144149
await this.runQueries(uris);
145150
}
146151

extensions/ql-vscode/src/queries-panel/query-discovery.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,15 @@ export class QueryDiscovery
5656
*
5757
* Trivial directories where there is only one child will be collapsed into a single node.
5858
*/
59-
public buildQueryTree(): Array<FileTreeNode<string>> {
59+
public buildQueryTree(): Array<FileTreeNode<string>> | undefined {
60+
const pathData = this.getPathData();
61+
if (pathData === undefined) {
62+
return undefined;
63+
}
64+
6065
const roots = [];
6166
for (const workspaceFolder of getOnDiskWorkspaceFoldersObjects()) {
62-
const queriesInRoot = this.getPathData().filter((query) =>
67+
const queriesInRoot = pathData.filter((query) =>
6368
containsPath(workspaceFolder.uri.fsPath, query.path),
6469
);
6570
if (queriesInRoot.length === 0) {

extensions/ql-vscode/src/queries-panel/query-pack-discovery.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,13 @@ export class QueryPackDiscovery extends FilePathDiscovery<QueryPack> {
3636
* or the pack's language is unknown.
3737
*/
3838
public getLanguageForQueryFile(queryPath: string): QueryLanguage | undefined {
39+
const pathData = this.getPathData();
40+
if (pathData === undefined) {
41+
return undefined;
42+
}
43+
3944
// Find all packs in a higher directory than the query
40-
const packs = this.getPathData().filter((queryPack) =>
45+
const packs = pathData.filter((queryPack) =>
4146
containsPath(dirname(queryPack.path), queryPath),
4247
);
4348

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

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import { Event, EventEmitter, TreeDataProvider, TreeItem } from "vscode";
2-
import { QueryTreeViewItem } from "./query-tree-view-item";
2+
import {
3+
QueryTreeViewItem,
4+
createQueryTreeFileItem,
5+
createQueryTreeFolderItem,
6+
createQueryTreeTextItem,
7+
} from "./query-tree-view-item";
38
import { DisposableObject } from "../common/disposable-object";
49
import { FileTreeNode } from "../common/file-tree-nodes";
510

611
export interface QueryDiscoverer {
7-
readonly buildQueryTree: () => Array<FileTreeNode<string>>;
12+
readonly buildQueryTree: () => Array<FileTreeNode<string>> | undefined;
813
readonly onDidChangeQueries: Event<void>;
914
}
1015

@@ -34,19 +39,37 @@ export class QueryTreeDataProvider
3439
}
3540

3641
private createTree(): QueryTreeViewItem[] {
37-
return this.queryDiscoverer
38-
.buildQueryTree()
39-
.map(this.convertFileTreeNode.bind(this));
42+
const queryTree = this.queryDiscoverer.buildQueryTree();
43+
if (queryTree === undefined) {
44+
return [];
45+
} else if (queryTree.length === 0) {
46+
return [this.noQueriesTreeViewItem()];
47+
} else {
48+
return queryTree.map(this.convertFileTreeNode.bind(this));
49+
}
4050
}
4151

4252
private convertFileTreeNode(
4353
fileTreeDirectory: FileTreeNode<string>,
4454
): QueryTreeViewItem {
45-
return new QueryTreeViewItem(
46-
fileTreeDirectory.name,
47-
fileTreeDirectory.path,
48-
fileTreeDirectory.data,
49-
fileTreeDirectory.children.map(this.convertFileTreeNode.bind(this)),
55+
if (fileTreeDirectory.children.length === 0) {
56+
return createQueryTreeFileItem(
57+
fileTreeDirectory.name,
58+
fileTreeDirectory.path,
59+
fileTreeDirectory.data,
60+
);
61+
} else {
62+
return createQueryTreeFolderItem(
63+
fileTreeDirectory.name,
64+
fileTreeDirectory.path,
65+
fileTreeDirectory.children.map(this.convertFileTreeNode.bind(this)),
66+
);
67+
}
68+
}
69+
70+
private noQueriesTreeViewItem(): QueryTreeViewItem {
71+
return createQueryTreeTextItem(
72+
"This workspace doesn't contain any CodeQL queries at the moment.",
5073
);
5174
}
5275

extensions/ql-vscode/src/queries-panel/query-tree-view-item.ts

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,43 @@ import * as vscode from "vscode";
33
export class QueryTreeViewItem extends vscode.TreeItem {
44
constructor(
55
name: string,
6-
public readonly path: string,
7-
language: string | undefined,
6+
public readonly path: string | undefined,
87
public readonly children: QueryTreeViewItem[],
98
) {
109
super(name);
11-
this.tooltip = path;
12-
if (this.children.length === 0) {
13-
this.description = language;
14-
this.collapsibleState = vscode.TreeItemCollapsibleState.None;
15-
this.contextValue = "queryFile";
16-
this.command = {
17-
title: "Open",
18-
command: "vscode.open",
19-
arguments: [vscode.Uri.file(path)],
20-
};
21-
} else {
22-
this.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
23-
this.contextValue = "queryFolder";
24-
}
2510
}
2611
}
12+
13+
export function createQueryTreeFolderItem(
14+
name: string,
15+
path: string,
16+
children: QueryTreeViewItem[],
17+
): QueryTreeViewItem {
18+
const item = new QueryTreeViewItem(name, path, children);
19+
item.tooltip = path;
20+
item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
21+
item.contextValue = "queryFolder";
22+
return item;
23+
}
24+
25+
export function createQueryTreeFileItem(
26+
name: string,
27+
path: string,
28+
language: string | undefined,
29+
): QueryTreeViewItem {
30+
const item = new QueryTreeViewItem(name, path, []);
31+
item.tooltip = path;
32+
item.description = language;
33+
item.collapsibleState = vscode.TreeItemCollapsibleState.None;
34+
item.contextValue = "queryFile";
35+
item.command = {
36+
title: "Open",
37+
command: "vscode.open",
38+
arguments: [vscode.Uri.file(path)],
39+
};
40+
return item;
41+
}
42+
43+
export function createQueryTreeTextItem(text: string): QueryTreeViewItem {
44+
return new QueryTreeViewItem(text, undefined, []);
45+
}

extensions/ql-vscode/src/variant-analysis/variant-analysis-manager.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ import {
7676
showAndLogInformationMessage,
7777
showAndLogWarningMessage,
7878
} from "../common/logging";
79-
import type { QueryTreeViewItem } from "../queries-panel/query-tree-view-item";
79+
import { QueryTreeViewItem } from "../queries-panel/query-tree-view-item";
8080

8181
const maxRetryCount = 3;
8282

@@ -191,7 +191,11 @@ export class VariantAnalysisManager
191191
private async runVariantAnalysisFromQueriesPanel(
192192
queryTreeViewItem: QueryTreeViewItem,
193193
): Promise<void> {
194-
await this.runVariantAnalysisFromCommand(Uri.file(queryTreeViewItem.path));
194+
if (queryTreeViewItem.path !== undefined) {
195+
await this.runVariantAnalysisFromCommand(
196+
Uri.file(queryTreeViewItem.path),
197+
);
198+
}
195199
}
196200

197201
public async runVariantAnalysis(

extensions/ql-vscode/test/vscode-tests/minimal-workspace/common/vscode/file-path-discovery.test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class TestFilePathDiscovery extends FilePathDiscovery<TestData> {
3434
return this.onDidChangePathData;
3535
}
3636

37-
public getPathData(): readonly TestData[] {
37+
public getPathData(): readonly TestData[] | undefined {
3838
return super.getPathData();
3939
}
4040

@@ -123,6 +123,10 @@ describe("FilePathDiscovery", () => {
123123
});
124124

125125
describe("initialRefresh", () => {
126+
it("should return undefined until initialRefresh is called", async () => {
127+
expect(discovery.getPathData()).toEqual(undefined);
128+
});
129+
126130
it("should handle no files being present", async () => {
127131
await discovery.initialRefresh();
128132
expect(discovery.getPathData()).toEqual([]);
@@ -155,6 +159,26 @@ describe("FilePathDiscovery", () => {
155159
new Set([{ path: join(workspacePath, "1.test"), contents: "1" }]),
156160
);
157161
});
162+
163+
it("should trigger listener when paths are found", async () => {
164+
makeTestFile(join(workspacePath, "123.test"));
165+
166+
const didChangePathsListener = jest.fn();
167+
discovery.onDidChangePaths(didChangePathsListener);
168+
169+
await discovery.initialRefresh();
170+
171+
expect(didChangePathsListener).toHaveBeenCalled();
172+
});
173+
174+
it("should trigger listener when no paths are found", async () => {
175+
const didChangePathsListener = jest.fn();
176+
discovery.onDidChangePaths(didChangePathsListener);
177+
178+
await discovery.initialRefresh();
179+
180+
expect(didChangePathsListener).toHaveBeenCalled();
181+
});
158182
});
159183

160184
describe("file added", () => {

extensions/ql-vscode/test/vscode-tests/minimal-workspace/queries-panel/query-discovery.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ describe("Query pack discovery", () => {
5454
});
5555

5656
describe("buildQueryTree", () => {
57+
it("returns undefined before initial refresh has been done", async () => {
58+
expect(discovery.buildQueryTree()).toEqual(undefined);
59+
});
60+
5761
it("returns an empty tree when there are no query files", async () => {
5862
await discovery.initialRefresh();
5963

0 commit comments

Comments
 (0)