diff --git a/cmd/core-lint/main_test.go b/cmd/core-lint/main_test.go index 28ba1c4..ab01df6 100644 --- a/cmd/core-lint/main_test.go +++ b/cmd/core-lint/main_test.go @@ -23,6 +23,8 @@ var ( func TestCLI_Run_JSON(t *testing.T) { dir := t.TempDir() + buildCLI(t) + t.Setenv("PATH", t.TempDir()) require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/test\n"), 0o644)) require.NoError(t, os.WriteFile(filepath.Join(dir, "input.go"), []byte(`package sample diff --git a/pkg/lint/adapter.go b/pkg/lint/adapter.go index 276e281..fcbbc66 100644 --- a/pkg/lint/adapter.go +++ b/pkg/lint/adapter.go @@ -169,13 +169,14 @@ func (adapter CommandAdapter) Run(ctx context.Context, input RunInput, files []s return result } + result.Tool.Version = probeCommandVersion(binary, input.Path) + runContext, cancel := context.WithTimeout(ctx, 5*time.Minute) defer cancel() args := adapter.buildArgs(input.Path, files) stdout, stderr, exitCode, runErr := runCommand(runContext, input.Path, binary, args) - result.Tool.Version = "" result.Tool.Duration = time.Since(startedAt).Round(time.Millisecond).String() if errors.Is(runContext.Err(), context.DeadlineExceeded) { @@ -233,6 +234,29 @@ func (adapter CommandAdapter) Run(ctx context.Context, input RunInput, files []s return result } +func probeCommandVersion(binary string, workingDir string) string { + for _, args := range [][]string{{"--version"}, {"-version"}, {"version"}} { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + stdout, stderr, exitCode, err := runCommand(ctx, workingDir, binary, args) + cancel() + + if err != nil && exitCode != 0 { + continue + } + + version := firstNonEmpty(stdout, stderr) + if version == "" { + continue + } + + if line := firstVersionLine(version); line != "" { + return line + } + } + + return "" +} + func (adapter CommandAdapter) availableBinary() (string, bool) { for _, binary := range adapter.binaries { path, err := exec.LookPath(binary) @@ -812,6 +836,16 @@ func firstNonEmpty(values ...string) string { return "" } +func firstVersionLine(output string) string { + for line := range strings.SplitSeq(strings.TrimSpace(output), "\n") { + line = strings.TrimSpace(line) + if line != "" { + return line + } + } + return "" +} + func slicesContains(values []string, target string) bool { for _, value := range values { if value == target { diff --git a/pkg/lint/service_test.go b/pkg/lint/service_test.go index e4aec95..f9bfe9d 100644 --- a/pkg/lint/service_test.go +++ b/pkg/lint/service_test.go @@ -26,7 +26,7 @@ func Run() { } `), 0o644)) - svc := NewService() + svc := &Service{adapters: []Adapter{newCatalogAdapter()}} report, err := svc.Run(context.Background(), RunInput{ Path: dir, FailOn: "warning", @@ -76,7 +76,7 @@ func run2() { runTestCommand(t, dir, "git", "add", "go.mod", "staged.go") - svc := NewService() + svc := &Service{adapters: []Adapter{newCatalogAdapter()}} report, err := svc.Run(context.Background(), RunInput{ Path: dir, Hook: true, @@ -118,6 +118,44 @@ func TestServiceRun_JS_PrettierFindings(t *testing.T) { assert.Equal(t, 1, report.Tools[0].Findings) } +func TestServiceRun_CapturesToolVersion(t *testing.T) { + dir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(dir, "package.json"), []byte("{\n \"name\": \"example\"\n}\n"), 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "index.js"), []byte("const value = 1;\n"), 0o644)) + + binDir := t.TempDir() + scriptPath := filepath.Join(binDir, "prettier") + script := `#!/bin/sh +case "$1" in + --version) + echo "prettier 3.2.1" + exit 0 + ;; + --list-different) + echo "index.js" + exit 1 + ;; +esac +echo "unexpected args: $*" >&2 +exit 0 +` + require.NoError(t, os.WriteFile(scriptPath, []byte(script), 0o755)) + t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH")) + + svc := &Service{adapters: []Adapter{ + newCommandAdapter("prettier", []string{"prettier"}, []string{"js"}, "style", "", false, true, pathArgs("--list-different"), parsePrettierDiagnostics), + }} + report, err := svc.Run(context.Background(), RunInput{ + Path: dir, + FailOn: "warning", + }) + require.NoError(t, err) + + require.Len(t, report.Tools, 1) + assert.Equal(t, "prettier", report.Tools[0].Name) + assert.Equal(t, "prettier 3.2.1", report.Tools[0].Version) +} + func TestServiceRun_Good_DeduplicatesMergedFindings(t *testing.T) { dir := t.TempDir() require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/test\n"), 0o644))