internal/gocommand: add a GoVersion function to avoid duplicating logic

It seems easier to have this as a shared internal function.

Updates golang/go#41598

Change-Id: If35bdbdf5499624dd55ae9daede1ff1ae9495026
Reviewed-on: https://go-review.googlesource.com/c/tools/+/263985
Trust: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Peter Weinberger <pjw@google.com>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/go/packages/golist.go b/go/packages/golist.go
index 2696cfb..787783c 100644
--- a/go/packages/golist.go
+++ b/go/packages/golist.go
@@ -91,7 +91,7 @@
 
 	goVersionOnce  sync.Once
 	goVersionError error
-	goVersion      string // third field of 'go version'
+	goVersion      int // The X in Go 1.X.
 
 	// vendorDirs caches the (non)existence of vendor directories.
 	vendorDirs map[string]bool
@@ -731,7 +731,7 @@
 
 	// On Go 1.14 and earlier, only add filenames from errors if the import stack is empty.
 	// The import stack behaves differently for these versions than newer Go versions.
-	if strings.HasPrefix(goV, "go1.13") || strings.HasPrefix(goV, "go1.14") {
+	if goV < 15 {
 		return len(p.Error.ImportStack) == 0
 	}
 
@@ -742,31 +742,9 @@
 	return len(p.Error.ImportStack) == 0 || p.Error.ImportStack[len(p.Error.ImportStack)-1] == p.ImportPath
 }
 
-func (state *golistState) getGoVersion() (string, error) {
+func (state *golistState) getGoVersion() (int, error) {
 	state.goVersionOnce.Do(func() {
-		var b *bytes.Buffer
-		// Invoke go version. Don't use invokeGo because it will supply build flags, and
-		// go version doesn't expect build flags.
-		inv := gocommand.Invocation{
-			Verb: "version",
-			Env:  state.cfg.Env,
-			Logf: state.cfg.Logf,
-		}
-		gocmdRunner := state.cfg.gocmdRunner
-		if gocmdRunner == nil {
-			gocmdRunner = &gocommand.Runner{}
-		}
-		b, _, _, state.goVersionError = gocmdRunner.RunRaw(state.cfg.Context, inv)
-		if state.goVersionError != nil {
-			return
-		}
-
-		sp := strings.Split(b.String(), " ")
-		if len(sp) < 3 {
-			state.goVersionError = fmt.Errorf("go version output: expected 'go version <version>', got '%s'", b.String())
-			return
-		}
-		state.goVersion = sp[2]
+		state.goVersion, state.goVersionError = gocommand.GoVersion(state.ctx, state.cfgInvocation(), state.cfg.gocmdRunner)
 	})
 	return state.goVersion, state.goVersionError
 }
diff --git a/internal/gocommand/version.go b/internal/gocommand/version.go
new file mode 100644
index 0000000..60d45ac
--- /dev/null
+++ b/internal/gocommand/version.go
@@ -0,0 +1,40 @@
+// Copyright 2020 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 gocommand
+
+import (
+	"context"
+	"fmt"
+	"strings"
+)
+
+// GoVersion checks the go version by running "go list" with modules off.
+// It returns the X in Go 1.X.
+func GoVersion(ctx context.Context, inv Invocation, r *Runner) (int, error) {
+	inv.Verb = "list"
+	inv.Args = []string{"-e", "-f", `{{context.ReleaseTags}}`}
+	inv.Env = append(append([]string{}, inv.Env...), "GO111MODULE=off")
+	// Unset any unneeded flags.
+	inv.ModFile = ""
+	inv.ModFlag = ""
+	stdoutBytes, err := r.Run(ctx, inv)
+	if err != nil {
+		return 0, err
+	}
+	stdout := stdoutBytes.String()
+	if len(stdout) < 3 {
+		return 0, fmt.Errorf("bad ReleaseTags output: %q", stdout)
+	}
+	// Split up "[go1.1 go1.15]"
+	tags := strings.Fields(stdout[1 : len(stdout)-2])
+	for i := len(tags) - 1; i >= 0; i-- {
+		var version int
+		if _, err := fmt.Sscanf(tags[i], "go1.%d", &version); err != nil {
+			continue
+		}
+		return version, nil
+	}
+	return 0, fmt.Errorf("no parseable ReleaseTags in %v", tags)
+}
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
index 9affdb5..733c937 100644
--- a/internal/lsp/cache/session.go
+++ b/internal/lsp/cache/session.go
@@ -650,34 +650,3 @@
 	}
 	return overlays
 }
-
-// goVersion returns the Go version in use for the given session.
-func (s *Session) goVersion(ctx context.Context, folder string, env []string) (int, error) {
-	// Check the go version by running "go list" with modules off.
-	// Borrowed from internal/imports/mod.go:620.
-	const format = `{{context.ReleaseTags}}`
-	inv := gocommand.Invocation{
-		Verb:       "list",
-		Args:       []string{"-e", "-f", format},
-		Env:        append(env, "GO111MODULE=off"),
-		WorkingDir: folder,
-	}
-	stdoutBytes, err := s.gocmdRunner.Run(ctx, inv)
-	if err != nil {
-		return 0, err
-	}
-	stdout := stdoutBytes.String()
-	if len(stdout) < 3 {
-		return 0, fmt.Errorf("bad ReleaseTags output: %q", stdout)
-	}
-	// Split up "[go1.1 go1.15]"
-	tags := strings.Fields(stdout[1 : len(stdout)-2])
-	for i := len(tags) - 1; i >= 0; i-- {
-		var version int
-		if _, err := fmt.Sscanf(tags[i], "go1.%d", &version); err != nil {
-			continue
-		}
-		return version, nil
-	}
-	return 0, fmt.Errorf("no parseable ReleaseTags in %v", tags)
-}
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index 8b3d307..b20182a 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -631,7 +631,11 @@
 		return nil, errors.Errorf("invalid workspace configuration: %w", err)
 	}
 	var err error
-	goversion, err := s.goVersion(ctx, folder.Filename(), options.EnvSlice())
+	inv := gocommand.Invocation{
+		WorkingDir: folder.Filename(),
+		Env:        options.EnvSlice(),
+	}
+	goversion, err := gocommand.GoVersion(ctx, inv, s.gocmdRunner)
 	if err != nil {
 		return nil, err
 	}