From 88b81a5fe41b3061b9f6840cf3609ddf5fc29cd5 Mon Sep 17 00:00:00 2001 From: Swarit Pandey Date: Wed, 8 Apr 2026 13:30:40 +0530 Subject: [PATCH] fix(cli): search-dir and html flag behaviour Signed-off-by: Swarit Pandey --- internal/cli/cli.go | 20 ++++++++--- internal/cli/cli_test.go | 72 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 7953d37..24fb375 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -59,7 +59,7 @@ func Parse(args []string) (*Config, error) { cfg.OutputFormat = "html" cfg.OutputFormatSet = true i++ - if i >= len(args) { + if i >= len(args) || looksLikeFlag(args[i]) { return nil, fmt.Errorf("--html requires a file path argument") } cfg.HTMLOutputFile = args[i] @@ -77,15 +77,14 @@ func Parse(args []string) (*Config, error) { cfg.ColorMode = mode case arg == "--search-dirs": i++ - if i >= len(args) || strings.HasPrefix(args[i], "--") { + if i >= len(args) || looksLikeFlag(args[i]) || isCommand(args[i]) { return nil, fmt.Errorf("--search-dirs requires at least one directory path argument") } if !searchDirsSet { cfg.SearchDirs = nil searchDirsSet = true } - // Greedily consume non-flag arguments - for i < len(args) && !strings.HasPrefix(args[i], "--") { + for i < len(args) && !looksLikeFlag(args[i]) && !isCommand(args[i]) { cfg.SearchDirs = append(cfg.SearchDirs, args[i]) i++ } @@ -156,3 +155,16 @@ Configuration: name, name, name, buildinfo.AgentURL) } + +func looksLikeFlag(s string) bool { + return strings.HasPrefix(s, "-") +} + +var commands = map[string]bool{ + "install": true, "uninstall": true, "send-telemetry": true, + "configure": true, "version": true, "help": true, +} + +func isCommand(s string) bool { + return commands[s] +} diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index 32cd6c5..b3d2c5d 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -181,3 +181,75 @@ func TestParse_FlagCombinations(t *testing.T) { t.Errorf("unexpected config: %+v", cfg) } } + +// Bug 1: --search-dirs greedily consumes single-dash flags and commands + +func TestParse_SearchDirsStopsAtCommand_Install(t *testing.T) { + cfg, err := Parse([]string{"--search-dirs", "/tmp", "install"}) + if err != nil { + t.Fatal(err) + } + if cfg.Command != "install" { + t.Errorf("expected command=install, got %q (search-dirs consumed it: %v)", cfg.Command, cfg.SearchDirs) + } + if len(cfg.SearchDirs) != 1 || cfg.SearchDirs[0] != "/tmp" { + t.Errorf("expected SearchDirs=[/tmp], got %v", cfg.SearchDirs) + } +} + +func TestParse_SearchDirsRejectsSingleDashFlag(t *testing.T) { + // --search-dirs -v should error, not silently consume -v as a directory + _, err := Parse([]string{"--search-dirs", "-v"}) + if err == nil { + t.Error("expected error when --search-dirs is followed by a flag") + } +} + +func TestParse_SearchDirsStopsAtCommand_Uninstall(t *testing.T) { + cfg, err := Parse([]string{"--search-dirs", "/opt", "uninstall"}) + if err != nil { + t.Fatal(err) + } + if cfg.Command != "uninstall" { + t.Errorf("expected command=uninstall, got %q (search-dirs consumed it: %v)", cfg.Command, cfg.SearchDirs) + } + if len(cfg.SearchDirs) != 1 || cfg.SearchDirs[0] != "/opt" { + t.Errorf("expected SearchDirs=[/opt], got %v", cfg.SearchDirs) + } +} + +func TestParse_SearchDirsStopsAtCommand_SendTelemetry(t *testing.T) { + cfg, err := Parse([]string{"--search-dirs", "/data", "send-telemetry"}) + if err != nil { + t.Fatal(err) + } + if cfg.Command != "send-telemetry" { + t.Errorf("expected command=send-telemetry, got %q (search-dirs consumed it: %v)", cfg.Command, cfg.SearchDirs) + } +} + +func TestParse_SearchDirsStopsAtCommand_Configure(t *testing.T) { + cfg, err := Parse([]string{"--search-dirs", "/data", "configure"}) + if err != nil { + t.Fatal(err) + } + if cfg.Command != "configure" { + t.Errorf("expected command=configure, got %q (search-dirs consumed it: %v)", cfg.Command, cfg.SearchDirs) + } +} + +// Bug 2: --html accepts flags as its filename argument + +func TestParse_HTMLRejectsFlag(t *testing.T) { + _, err := Parse([]string{"--html", "--verbose"}) + if err == nil { + t.Error("expected error when --html argument looks like a flag, got nil") + } +} + +func TestParse_HTMLRejectsDashFlag(t *testing.T) { + _, err := Parse([]string{"--html", "-v"}) + if err == nil { + t.Error("expected error when --html argument is -v, got nil") + } +}