internal/govulncheck: return exit code 0 for -json mode when vulns are found

When vulnerabilities are found, we return an exit code 3 if running in
text mode, because we assume that users are checking for whether
vulnerabilities exist in their code. Therefore, the presence of
vulnerabilities should be a failure, regardless of what is in the
output.

This isn't the case for -json mode, since the presence of JSON data
indicates success, so return an exit code of 0. This is consistent with
a decision from CL 432236.

Change-Id: Ie403ea79e40eb80bad590679a27ef9c3f5f8665a
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/474795
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Julie Qiu <julieqiu@google.com>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
Run-TryBot: Julie Qiu <julieqiu@google.com>
diff --git a/internal/govulncheck/print_test.go b/internal/govulncheck/print_test.go
index 048797b..97ad1b5 100644
--- a/internal/govulncheck/print_test.go
+++ b/internal/govulncheck/print_test.go
@@ -141,7 +141,7 @@
 
 	got := new(strings.Builder)
 	output := readableOutput{to: got}
-	if err := output.result(r, false, true); err != nil {
+	if err := output.result(r, false, true); err != nil && err != ErrVulnerabilitiesFound {
 		t.Fatal(err)
 	}
 	want := `No vulnerabilities found.
@@ -199,7 +199,7 @@
 
 	got := new(strings.Builder)
 	output := readableOutput{to: got}
-	if err := output.result(r, false, true); err != nil {
+	if err := output.result(r, false, true); err != nil && err != ErrVulnerabilitiesFound {
 		t.Fatal(err)
 	}
 	want := `Your code is affected by 1 vulnerability from 1 module.
@@ -266,7 +266,7 @@
 
 	got := new(strings.Builder)
 	output := readableOutput{to: got}
-	if err := output.result(r, false, false); err != nil {
+	if err := output.result(r, false, false); err != nil && err != ErrVulnerabilitiesFound {
 		t.Fatal(err)
 	}
 	want := `Your code is affected by 2 vulnerabilities from 1 module and the Go standard library.
@@ -328,7 +328,7 @@
 
 	got := new(strings.Builder)
 	output := readableOutput{to: got}
-	if err := output.result(r, false, true); err != nil {
+	if err := output.result(r, false, true); err != nil && err != ErrVulnerabilitiesFound {
 		t.Fatal(err)
 	}
 	want := `Your code is affected by 1 vulnerability from 2 modules.
diff --git a/internal/govulncheck/scan.go b/internal/govulncheck/scan.go
index 65eda61..3e531d1 100644
--- a/internal/govulncheck/scan.go
+++ b/internal/govulncheck/scan.go
@@ -155,31 +155,7 @@
 	if err != nil {
 		return err
 	}
-	if err := out.result(res, c.verbose, c.sourceAnalysis); err != nil {
-		return err
-	}
-
-	// Return exit status -3 if some vulnerabilities are actually
-	// called in source mode or just present in binary mode.
-	//
-	// This follows the style from
-	// golang.org/x/tools/go/analysis/singlechecker,
-	// which fails with 3 if there are some findings.
-	if c.sourceAnalysis {
-		for _, v := range res.Vulns {
-			if v.IsCalled() {
-				return ErrVulnerabilitiesFound
-			}
-		}
-	} else if len(res.Vulns) > 0 {
-		return ErrVulnerabilitiesFound
-	}
-	return nil
-}
-
-// jsonFail prints an error to stdout in the format {Error: errorString}
-func jsonFail(err error) {
-	fmt.Printf("{\"Error\": %q}\n", err)
+	return out.result(res, c.verbose, c.sourceAnalysis)
 }
 
 func isFile(path string) bool {
diff --git a/internal/govulncheck/text.go b/internal/govulncheck/text.go
index 1fd4759..2f3e5cc 100644
--- a/internal/govulncheck/text.go
+++ b/internal/govulncheck/text.go
@@ -102,7 +102,28 @@
 	if err != nil {
 		return err
 	}
-	return tmpl.Execute(o.to, tmplRes)
+	if err := tmpl.Execute(o.to, tmplRes); err != nil {
+		return err
+	}
+
+	// Return exit status -3 if some vulnerabilities are actually
+	// called in source mode or just present in binary mode.
+	//
+	// This follows the style from
+	// golang.org/x/tools/go/analysis/singlechecker,
+	// which fails with 3 if there are some findings.
+	//
+	// TODO(https://go.dev/issue/58945): add a test for this
+	if source {
+		for _, v := range r.Vulns {
+			if v.IsCalled() {
+				return ErrVulnerabilitiesFound
+			}
+		}
+	} else if len(r.Vulns) > 0 {
+		return ErrVulnerabilitiesFound
+	}
+	return nil
 }
 
 func (o *readableOutput) progress(msg string) {