Skip to content

Commit

Permalink
Extend CodeLens to build and run more targets
Browse files Browse the repository at this point in the history
Previously, `_binary` targets could only be run, `_test` taregets could
only be tested, and all other targets could only be built. This change
allows all targets to be built and all non-`_library` targets to be run.

Additionally, this change shortens the names of the targets in codelens,
as otherwise the additional commands caused a lot of clutter:
`Run //foo/bar/baz:flubber  Build //foo/bar/baz:flubber` etc.
  • Loading branch information
cramertj committed Jun 29, 2024
1 parent 2f9d49c commit c4cde6e
Showing 1 changed file with 93 additions and 28 deletions.
121 changes: 93 additions & 28 deletions src/codelens/bazel_build_code_lens_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import * as path from "path";
import * as vscode from "vscode";

import { BazelWorkspaceInfo, QueryLocation } from "../bazel";
Expand All @@ -20,6 +21,48 @@ import { getDefaultBazelExecutablePath } from "../extension/configuration";
import { blaze_query } from "../protos";
import { CodeLensCommandAdapter } from "./code_lens_command_adapter";

/** Returns a target string without some prefix.
*
* If the target string does not have the prefix, returns the string unchanged.
*
* @param target The string whose prefix to remove.
* @param prefix The prefix to remove, if present.
* @returns The target string without the provided prefix.
*/
function withoutPrefix(target: string, prefix: string): string {
if (!target.startsWith(prefix)) {
return target;
}
return target.substring(prefix.length);
}

/** Computes the shortened name of a Bazel target.
*
* For example, if the target name starts with `//foo/bar/baz:fizbuzz` and we're
* in `foo/bar/baz/BUILD.bazel`, the target's short name will be `fizzbuzz`.
*
* This allows our code lens suggestions to avoid filling users' screen with
* redundant path information.
*
* @param targetName The unshortened name of the target.
* @param bazelWorkspaceInfo The currrent Bazel workspace.
* @param location The location of the target's BUILD.bazel file.
*
* @returns The shortened name of the target.
*/
function getTargetShortName(
targetName: string,
bazelWorkspaceInfo: BazelWorkspaceInfo,
location: QueryLocation,
): string {
const pathInWorkspace = path.dirname(
path.relative(bazelWorkspaceInfo.bazelWorkspacePath, location.path),
);
// On Windows, path separators are different from Bazel's `/` separator.
const bazelStylePathInWorkspace = pathInWorkspace.replaceAll(path.sep, "/");
return withoutPrefix(targetName, "//" + bazelStylePathInWorkspace + ":");
}

/** Provids CodeLenses for targets in Bazel BUILD files. */
export class BazelBuildCodeLensProvider implements vscode.CodeLensProvider {
public onDidChangeCodeLenses: vscode.Event<void>;
Expand Down Expand Up @@ -107,40 +150,62 @@ export class BazelBuildCodeLensProvider implements vscode.CodeLensProvider {
): vscode.CodeLens[] {
const result: vscode.CodeLens[] = [];

interface LensCommand {
commandString: string;
name: string;
}

for (const target of queryResult.target) {
const location = new QueryLocation(target.rule.location);
const targetName = target.rule.name;
const ruleClass = target.rule.ruleClass;
let cmd: vscode.Command;
let targetShortName = getTargetShortName(

Check failure on line 162 in src/codelens/bazel_build_code_lens_provider.ts

View workflow job for this annotation

GitHub Actions / Build VS Code extension

'targetShortName' is never reassigned. Use 'const' instead
targetName,
bazelWorkspaceInfo,
location,
);

const commands: LensCommand[] = [];

// Only test targets support testing.
if (ruleClass.endsWith("_test") || ruleClass === "test_suite") {
cmd = {
arguments: [
new CodeLensCommandAdapter(bazelWorkspaceInfo, [targetName]),
],
command: "bazel.testTarget",
title: `Test ${targetName}`,
tooltip: `Test ${targetName}`,
};
} else if (ruleClass.endsWith("_binary")) {
cmd = {
arguments: [
new CodeLensCommandAdapter(bazelWorkspaceInfo, [targetName]),
],
command: "bazel.runTarget",
title: `Run ${targetName}`,
tooltip: `Run ${targetName}`,
};
} else {
cmd = {
arguments: [
new CodeLensCommandAdapter(bazelWorkspaceInfo, [targetName]),
],
command: "bazel.buildTarget",
title: `Build ${targetName}`,
tooltip: `Build ${targetName}`,
};
commands.push({
commandString: "bazel.testTarget",
name: "Test",
});
}

// Targets which are not libraries may support running.
//
// Without checking the Bazel rule's `executable` attribute we can't know
// for sure which targets can be run.
const ruleIsLibrary = ruleClass.endsWith("_library");
if (!ruleIsLibrary) {
commands.push({
commandString: "bazel.runTarget",
name: "Run",
});
}

// All targets support building.
commands.push({
commandString: "bazel.buildTarget",
name: "Build",
});

for (const command of commands) {
const title = `${command.name} ${targetShortName}`;
result.push(
new vscode.CodeLens(location.range, {
arguments: [
new CodeLensCommandAdapter(bazelWorkspaceInfo, [targetName]),
],
command: command.commandString,
title,
tooltip: title,
}),
);
}
result.push(new vscode.CodeLens(location.range, cmd));
}

return result;
Expand Down

0 comments on commit c4cde6e

Please sign in to comment.