feat(cli): add --grep as alias for -t/--test-name-pattern in bun test (#25788)

This commit is contained in:
robobun
2026-01-02 04:52:47 -08:00
committed by GitHub
parent 0141a4fac9
commit 779764332a
4 changed files with 85 additions and 6 deletions

View File

@@ -218,7 +218,7 @@ pub const test_only_params = [_]ParamType{
clap.parseParam("--coverage-reporter <STR>... Report coverage in 'text' and/or 'lcov'. Defaults to 'text'.") catch unreachable,
clap.parseParam("--coverage-dir <STR> Directory for coverage files. Defaults to 'coverage'.") catch unreachable,
clap.parseParam("--bail <NUMBER>? Exit the test suite after <NUMBER> failures. If you do not specify a number, it defaults to 1.") catch unreachable,
clap.parseParam("-t, --test-name-pattern <STR> Run only tests with a name that matches the given regex.") catch unreachable,
clap.parseParam("-t, --test-name-pattern/--grep <STR> Run only tests with a name that matches the given regex.") catch unreachable,
clap.parseParam("--reporter <STR> Test output reporter format. Available: 'junit' (requires --reporter-outfile), 'dots'. Default: console output.") catch unreachable,
clap.parseParam("--reporter-outfile <STR> Output file path for the reporter format (required with --reporter).") catch unreachable,
clap.parseParam("--dots Enable dots reporter. Shorthand for --reporter=dots.") catch unreachable,

View File

@@ -8,8 +8,22 @@ pub const Names = struct {
/// '-' prefix
short: ?u8 = null,
/// '--' prefix
/// '--' prefix (primary name, used for display/help)
long: ?[]const u8 = null,
/// Additional '--' prefixed aliases (e.g., --grep as alias for --test-name-pattern)
long_aliases: []const []const u8 = &.{},
/// Check if the given name matches the primary long name or any alias
pub fn matchesLong(self: Names, name: []const u8) bool {
if (self.long) |l| {
if (mem.eql(u8, name, l)) return true;
}
for (self.long_aliases) |alias| {
if (mem.eql(u8, name, alias)) return true;
}
return false;
}
};
/// Whether a param takes no value (a flag), one value, or can be specified multiple times.
@@ -51,6 +65,7 @@ pub fn Param(comptime Id: type) type {
/// Takes a string and parses it to a Param(Help).
/// This is the reverse of 'help' but for at single parameter only.
/// Supports multiple long name variants separated by '/' (e.g., "--test-name-pattern/--grep").
pub fn parseParam(line: []const u8) !Param(Help) {
@setEvalBranchQuota(999999);
@@ -89,11 +104,67 @@ pub fn parseParam(line: []const u8) !Param(Help) {
} else null;
var res = parseParamRest(it.rest());
res.names.long = param_str[2..];
res.names.short = short_name;
// Parse long names - supports multiple variants separated by '/'
// e.g., "--test-name-pattern/--grep" becomes primary "test-name-pattern" with alias "grep"
const long_names = parseLongNames(param_str);
res.names.long = long_names.long;
res.names.long_aliases = long_names.long_aliases;
return res;
}
fn parseLongNames(comptime param_str: []const u8) Names {
comptime {
// Count how many long name variants we have (separated by '/')
var alias_count: usize = 0;
for (param_str) |c| {
if (c == '/') alias_count += 1;
}
if (alias_count == 0) {
// No aliases, just the primary name
if (mem.startsWith(u8, param_str, "--")) {
return .{ .long = param_str[2..], .long_aliases = &.{} };
}
return .{ .long = null, .long_aliases = &.{} };
}
// Parse multiple long names at comptime
// First pass: find the primary name
var primary: ?[]const u8 = null;
var name_it = mem.splitScalar(u8, param_str, '/');
while (name_it.next()) |name_part| {
if (!mem.startsWith(u8, name_part, "--")) continue;
primary = name_part[2..];
break;
}
// Second pass: collect aliases into a comptime-known array type
const aliases = blk: {
var result: [alias_count][]const u8 = undefined;
var idx: usize = 0;
var it = mem.splitScalar(u8, param_str, '/');
var is_first = true;
while (it.next()) |name_part| {
if (!mem.startsWith(u8, name_part, "--")) continue;
if (is_first) {
is_first = false;
continue; // Skip primary
}
result[idx] = name_part[2..];
idx += 1;
}
break :blk result;
};
return .{
.long = primary,
.long_aliases = &aliases,
};
}
}
fn parseParamRest(line: []const u8) Param(Help) {
if (mem.startsWith(u8, line, "<")) blk: {
const len = mem.indexOfScalar(u8, line, '>') orelse break :blk;

View File

@@ -156,6 +156,11 @@ pub fn ComptimeClap(
if (mem.eql(u8, name, "--" ++ l))
return true;
}
// Check aliases
for (param.names.long_aliases) |alias| {
if (mem.eql(u8, name, "--" ++ alias))
return true;
}
}
return false;
@@ -173,6 +178,11 @@ pub fn ComptimeClap(
if (mem.eql(u8, name, "--" ++ l))
return param;
}
// Check aliases
for (param.names.long_aliases) |alias| {
if (mem.eql(u8, name, "--" ++ alias))
return param;
}
}
@compileError(name ++ " is not a parameter.");

View File

@@ -61,9 +61,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type {
const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null;
for (parser.params) |*param| {
const match = param.names.long orelse continue;
if (!mem.eql(u8, name, match))
if (!param.names.matchesLong(name))
continue;
if (param.takes_value == .none or param.takes_value == .one_optional) {