cmd/govulncheck: move package loading to internal/govulncheck

Move the code to load packages into internal/govulncheck so it
can be shared with gopls.

Change-Id: I3a3e6e055b3640639a5978cfd36791ea1884c9ad
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/406581
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/cmd/govulncheck/internal/govulncheck/source.go b/cmd/govulncheck/internal/govulncheck/source.go
new file mode 100644
index 0000000..155ddf1
--- /dev/null
+++ b/cmd/govulncheck/internal/govulncheck/source.go
@@ -0,0 +1,52 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package govulncheck
+
+import (
+	"fmt"
+	"strings"
+
+	"golang.org/x/tools/go/packages"
+	"golang.org/x/vuln/vulncheck"
+)
+
+// A PackageError contains errors from loading a set of packages.
+type PackageError struct {
+	Errors []packages.Error
+}
+
+func (e *PackageError) Error() string {
+	var b strings.Builder
+	fmt.Fprintln(&b, "Packages contain errors:")
+	for _, e := range e.Errors {
+		fmt.Println(&b, e)
+	}
+	return b.String()
+}
+
+// LoadPackages loads the packages matching patterns using cfg, after setting
+// the cfg mode flags that vulncheck needs for analysis.
+// If the packages contain errors, a PackageError is returned containing a list of the errors,
+// along with the packages themselves.
+func LoadPackages(cfg *packages.Config, patterns ...string) ([]*vulncheck.Package, error) {
+	cfg.Mode |= packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles |
+		packages.NeedImports | packages.NeedTypes | packages.NeedTypesSizes |
+		packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedDeps |
+		packages.NeedModule
+
+	pkgs, err := packages.Load(cfg, patterns...)
+	vpkgs := vulncheck.Convert(pkgs)
+	if err != nil {
+		return nil, err
+	}
+	var perrs []packages.Error
+	packages.Visit(pkgs, nil, func(p *packages.Package) {
+		perrs = append(perrs, p.Errors...)
+	})
+	if len(perrs) > 0 {
+		err = &PackageError{perrs}
+	}
+	return vpkgs, err
+}
diff --git a/cmd/govulncheck/main.go b/cmd/govulncheck/main.go
index 66ec2d7..a37dd23 100644
--- a/cmd/govulncheck/main.go
+++ b/cmd/govulncheck/main.go
@@ -99,7 +99,7 @@
 	patterns := flag.Args()
 	var (
 		r     *vulncheck.Result
-		pkgs  []*packages.Package
+		pkgs  []*vulncheck.Package
 		vulns []*vulncheck.Vuln
 	)
 	if len(patterns) == 1 && isFile(patterns[0]) {
@@ -115,7 +115,6 @@
 		vulns = r.Vulns
 	} else {
 		cfg := &packages.Config{
-			Mode:       packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedDeps | packages.NeedModule,
 			Tests:      *testsFlag,
 			BuildFlags: []string{fmt.Sprintf("-tags=%s", strings.Join(build.Default.BuildTags, ","))},
 		}
@@ -123,7 +122,7 @@
 		if err != nil {
 			die("govulncheck: %v", err)
 		}
-		r, err = vulncheck.Source(ctx, vulncheck.Convert(pkgs), vcfg)
+		r, err = vulncheck.Source(ctx, pkgs, vcfg)
 		if err != nil {
 			die("govulncheck: %v", err)
 		}
@@ -262,17 +261,14 @@
 	return !s.IsDir()
 }
 
-func loadPackages(cfg *packages.Config, patterns []string) ([]*packages.Package, error) {
+func loadPackages(cfg *packages.Config, patterns []string) ([]*vulncheck.Package, error) {
 	if *verboseFlag {
 		log.Println("loading packages...")
 	}
-	pkgs, err := packages.Load(cfg, patterns...)
+	pkgs, err := govulncheck.LoadPackages(cfg, patterns...)
 	if err != nil {
 		return nil, err
 	}
-	if packages.PrintErrors(pkgs) > 0 {
-		return nil, fmt.Errorf("packages contain errors")
-	}
 	if *verboseFlag {
 		log.Printf("\t%d loaded packages\n", len(pkgs))
 	}