Skip to content

Commit 61a1717

Browse files
committed
refactor(workspace-tree): Add BazelQuerier for workspace tree
Separate and abstract the tree view specific bazel queries in prepare for adding test cases. Also pull out the configuration getter since I found it simpler to use (auto complete and check by tsc, and maybe customizing the defaults based on more complicated logic).
1 parent 0bb1fe3 commit 61a1717

File tree

8 files changed

+160
-37
lines changed

8 files changed

+160
-37
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@
178178
},
179179
"bazel.commandLine.queryExpression": {
180180
"type": "string",
181-
"default": "...:*",
181+
"default": "",
182182
"description": "A [query language expression](https://bazel.build/query/language) which determines the packages displayed in the workspace tree and quick picker. The default inspects the entire workspace, but you could narrow it. For example: `//part/you/want/...:*`"
183183
},
184184
"bazel.lsp.command": {

src/bazel/bazel_utils.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,22 @@ import * as vscode from "vscode";
1818
import { blaze_query } from "../protos";
1919
import { BazelQuery } from "./bazel_query";
2020

21+
/**
22+
* Get the absolute path for a queried label.
23+
*
24+
* The queried package path are without leading double slash, while we want to
25+
* provide with leading slash.
26+
*
27+
* @param label The label.
28+
* @returns The label in absolute path.
29+
*/
30+
export function labelFromQueriedToAbsolute(label: string): string {
31+
// External packages are in form `@repo//foo/bar`.
32+
// Main repo relative label are in form `foo/bar`.
33+
// Main repo absolute label are in form `//foo/bar`.
34+
return label.includes("//") ? label : `//${label}`;
35+
}
36+
2137
/**
2238
* Get the package label for a build file.
2339
*

src/extension/configuration.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,11 @@ export function getDefaultBazelExecutablePath(): string {
3232
}
3333
return bazelExecutable;
3434
}
35+
36+
export function getDefaultQueryExpression(): string {
37+
return (
38+
vscode.workspace
39+
.getConfiguration("bazel.commandLine")
40+
.get<string>("queryExpression") ?? "...:*"
41+
);
42+
}

src/workspace-tree/bazel_package_tree_item.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,11 @@
1414

1515
import * as vscode from "vscode";
1616
import { BazelWorkspaceInfo } from "../bazel";
17-
import {
18-
BazelQuery,
19-
IBazelCommandAdapter,
20-
IBazelCommandOptions,
21-
} from "../bazel";
22-
import { getDefaultBazelExecutablePath } from "../extension/configuration";
17+
import { IBazelCommandAdapter, IBazelCommandOptions } from "../bazel";
2318
import { blaze_query } from "../protos";
2419
import { BazelTargetTreeItem } from "./bazel_target_tree_item";
2520
import { IBazelTreeItem } from "./bazel_tree_item";
21+
import { IBazelQuerier } from "./querier";
2622
import { Resources } from "../extension/resources";
2723

2824
/** A tree item representing a build package. */
@@ -33,7 +29,7 @@ export class BazelPackageTreeItem
3329
* The array of subpackages that should be shown directly under this package
3430
* item.
3531
*/
36-
public directSubpackages: BazelPackageTreeItem[] = [];
32+
public directSubpackages: IBazelTreeItem[] = [];
3733

3834
/**
3935
* Initializes a new tree item with the given workspace path and package path.
@@ -46,6 +42,7 @@ export class BazelPackageTreeItem
4642
*/
4743
constructor(
4844
private readonly resources: Resources,
45+
private readonly querier: IBazelQuerier,
4946
private readonly workspaceInfo: BazelWorkspaceInfo,
5047
private readonly packagePath: string,
5148
private readonly parentPackagePath: string,
@@ -56,21 +53,18 @@ export class BazelPackageTreeItem
5653
}
5754

5855
public async getChildren(): Promise<IBazelTreeItem[]> {
59-
const queryResult = await new BazelQuery(
60-
getDefaultBazelExecutablePath(),
61-
this.workspaceInfo.bazelWorkspacePath,
62-
).queryTargets(`//${this.packagePath}:all`, {
63-
ignoresErrors: true,
64-
sortByRuleName: true,
65-
});
56+
const queryResult = await this.querier.queryChildrenTargets(
57+
this.workspaceInfo,
58+
this.packagePath,
59+
);
6660
const targets = queryResult.target.map((target: blaze_query.ITarget) => {
6761
return new BazelTargetTreeItem(
6862
this.resources,
6963
this.workspaceInfo,
7064
target,
7165
);
7266
});
73-
return (this.directSubpackages as IBazelTreeItem[]).concat(targets);
67+
return this.directSubpackages.concat(targets);
7468
}
7569

7670
public getLabel(): string {

src/workspace-tree/bazel_workspace_folder_tree_item.ts

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@
1313
// limitations under the License.
1414

1515
import * as vscode from "vscode";
16-
import { BazelWorkspaceInfo, BazelQuery } from "../bazel";
17-
import { getDefaultBazelExecutablePath } from "../extension/configuration";
16+
import { BazelWorkspaceInfo } from "../bazel";
1817
import { blaze_query } from "../protos";
1918
import { BazelPackageTreeItem } from "./bazel_package_tree_item";
2019
import { BazelTargetTreeItem } from "./bazel_target_tree_item";
2120
import { IBazelTreeItem } from "./bazel_tree_item";
21+
import { IBazelQuerier } from "./querier";
2222
import { Resources } from "../extension/resources";
2323

2424
/** A tree item representing a workspace folder. */
@@ -30,6 +30,7 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
3030
*/
3131
constructor(
3232
private readonly resources: Resources,
33+
private readonly querier: IBazelQuerier,
3334
private readonly workspaceInfo: BazelWorkspaceInfo,
3435
) {}
3536

@@ -81,7 +82,7 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
8182
packagePaths: string[],
8283
startIndex: number,
8384
endIndex: number,
84-
treeItems: BazelPackageTreeItem[],
85+
treeItems: IBazelTreeItem[],
8586
parentPackagePath: string,
8687
) {
8788
// We can assume that the caller has sorted the packages, so we scan them to
@@ -127,6 +128,7 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
127128
// algorithm again to group its children.
128129
const item = new BazelPackageTreeItem(
129130
this.resources,
131+
this.querier,
130132
this.workspaceInfo,
131133
packagePath,
132134
parentPackagePath,
@@ -160,15 +162,7 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
160162
if (!this.workspaceInfo) {
161163
return Promise.resolve([] as IBazelTreeItem[]);
162164
}
163-
const workspacePath = this.workspaceInfo.workspaceFolder.uri.fsPath;
164-
const packagePaths = await new BazelQuery(
165-
getDefaultBazelExecutablePath(),
166-
workspacePath,
167-
).queryPackages(
168-
vscode.workspace
169-
.getConfiguration("bazel.commandLine")
170-
.get("queryExpression"),
171-
);
165+
const packagePaths = await this.querier.queryPackages(this.workspaceInfo);
172166
const topLevelItems: BazelPackageTreeItem[] = [];
173167
this.buildPackageTree(
174168
packagePaths,
@@ -180,13 +174,10 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
180174

181175
// Now collect any targets in the directory also (this can fail since
182176
// there might not be a BUILD files at this level (but down levels)).
183-
const queryResult = await new BazelQuery(
184-
getDefaultBazelExecutablePath(),
185-
workspacePath,
186-
).queryTargets(`:all`, {
187-
ignoresErrors: true,
188-
sortByRuleName: true,
189-
});
177+
const queryResult = await this.querier.queryChildrenTargets(
178+
this.workspaceInfo,
179+
"",
180+
);
190181
const targets = queryResult.target.map((target: blaze_query.ITarget) => {
191182
return new BazelTargetTreeItem(
192183
this.resources,

src/workspace-tree/bazel_workspace_tree_provider.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { BazelWorkspaceInfo } from "../bazel";
1717
import { IBazelTreeItem } from "./bazel_tree_item";
1818
import { BazelWorkspaceFolderTreeItem } from "./bazel_workspace_folder_tree_item";
1919
import { Resources } from "../extension/resources";
20+
import { IBazelQuerier, ProcessBazelQuerier } from "./querier";
2021

2122
/**
2223
* Provides a tree of Bazel build packages and targets for the VS Code explorer
@@ -46,9 +47,12 @@ export class BazelWorkspaceTreeProvider
4647
/**
4748
* Initializes a new tree provider with the given extension context.
4849
*
49-
* @param context The VS Code extension context.
50+
* @param querier The interface providing the `bazel query` results.
5051
*/
51-
constructor(private readonly resources: Resources) {
52+
constructor(
53+
private readonly resources: Resources,
54+
private readonly querier: IBazelQuerier = new ProcessBazelQuerier(),
55+
) {
5256
const buildFilesWatcher = vscode.workspace.createFileSystemWatcher(
5357
"**/{BUILD,BUILD.bazel}",
5458
false,
@@ -137,6 +141,7 @@ export class BazelWorkspaceTreeProvider
137141
if (workspaceInfo) {
138142
return new BazelWorkspaceFolderTreeItem(
139143
this.resources,
144+
this.querier,
140145
workspaceInfo,
141146
);
142147
}

src/workspace-tree/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@
1313
// limitations under the License.
1414

1515
export * from "./bazel_workspace_tree_provider";
16+
export * from "./querier";

src/workspace-tree/querier.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright 2024 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import * as vscode from "vscode";
16+
import {
17+
BazelQuery,
18+
BazelWorkspaceInfo,
19+
labelFromQueriedToAbsolute,
20+
} from "../bazel";
21+
import {
22+
getDefaultBazelExecutablePath,
23+
getDefaultQueryExpression,
24+
} from "../extension/configuration";
25+
import { blaze_query } from "../protos";
26+
import { BazelInfo } from "../bazel/bazel_info";
27+
28+
/**
29+
* Bazel querier for workspace tree.
30+
*
31+
* The interface defined here is to specifying the operation required for a
32+
* workspace tree instead of all bazel query syntax and options supported.
33+
*
34+
* The function named with queryXxx are all for querying bazel informations.
35+
*/
36+
export interface IBazelQuerier {
37+
/**
38+
* Queries bazel workspace path by given vscode workspace folder.
39+
*
40+
* @param workspaceInfo the Bazel workspace info.
41+
* @returns package name queries in absolute apparent paths.
42+
*/
43+
queryWorkspace(
44+
workspaceFolder: vscode.WorkspaceFolder,
45+
): Thenable<BazelWorkspaceInfo | undefined>;
46+
47+
/**
48+
* Queries all Bazel packages in a workspace folder.
49+
*
50+
* @param workspaceInfo the Bazel workspace info.
51+
* @returns package name queries in absolute apparent paths.
52+
*/
53+
queryPackages(workspaceInfo: BazelWorkspaceInfo): Thenable<string[]>;
54+
55+
/**
56+
* Queries all children targets of a Bazel package.
57+
*
58+
* @param workspaceInfo the Bazel workspace info.
59+
* @param packagePath the Bazel package path. Could be either in absolute label or
60+
* relative to the opening vscode workspace in `workspaceInfo`.
61+
*/
62+
queryChildrenTargets(
63+
workspaceInfo: BazelWorkspaceInfo,
64+
packagePath: string,
65+
): Thenable<blaze_query.IQueryResult>;
66+
}
67+
68+
/**
69+
* Calling Bazel process for the queries.
70+
*/
71+
export class ProcessBazelQuerier implements IBazelQuerier {
72+
async queryWorkspace(
73+
workspaceFolder: vscode.WorkspaceFolder,
74+
): Promise<BazelWorkspaceInfo | undefined> {
75+
try {
76+
const bazelWorkspacePath = await new BazelInfo(
77+
getDefaultBazelExecutablePath(),
78+
workspaceFolder.uri.fsPath,
79+
).getOne("workspace");
80+
return new BazelWorkspaceInfo(bazelWorkspacePath, workspaceFolder);
81+
} catch {
82+
return undefined;
83+
}
84+
}
85+
86+
async queryPackages(workspaceInfo: BazelWorkspaceInfo): Promise<string[]> {
87+
const packages = await new BazelQuery(
88+
getDefaultBazelExecutablePath(),
89+
workspaceInfo.workspaceFolder.uri.fsPath,
90+
).queryPackages(getDefaultQueryExpression());
91+
return packages.map(labelFromQueriedToAbsolute);
92+
}
93+
94+
queryChildrenTargets(
95+
workspaceInfo: BazelWorkspaceInfo,
96+
packagePath: string,
97+
): Promise<blaze_query.IQueryResult> {
98+
// Getting all rules without files, thus using :all instead of :*.
99+
const query = `${packagePath}:all`;
100+
return new BazelQuery(
101+
getDefaultBazelExecutablePath(),
102+
workspaceInfo.workspaceFolder.uri.fsPath,
103+
).queryTargets(query, {
104+
ignoresErrors: true,
105+
sortByRuleName: true,
106+
});
107+
}
108+
}

0 commit comments

Comments
 (0)