Skip to content

Commit

Permalink
gopls/internal/lsp: warn about Go versions that are too old
Browse files Browse the repository at this point in the history
Add a showMessage notification when the Go version in PATH is too old.

Also delete the unused View.Rebuild method.

Updates golang/go#50825

Change-Id: I279a04f021a0f8ddb09fcfe299fbab8d10e8c022
Reviewed-on: https://go-review.googlesource.com/c/tools/+/439836
Run-TryBot: Robert Findley <[email protected]>
Auto-Submit: Robert Findley <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
gopls-CI: kokoro <[email protected]>
Reviewed-by: Hyang-Ah Hana Kim <[email protected]>
  • Loading branch information
findleyr authored and gopherbot committed Oct 7, 2022
1 parent 709f108 commit 20c1ee7
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 21 deletions.
13 changes: 4 additions & 9 deletions gopls/internal/lsp/cache/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,15 +317,6 @@ func (v *View) SetOptions(ctx context.Context, options *source.Options) (source.
return newView, err
}

func (v *View) Rebuild(ctx context.Context) (source.Snapshot, func(), error) {
newView, err := v.session.updateView(ctx, v, v.Options())
if err != nil {
return nil, func() {}, err
}
snapshot, release := newView.Snapshot(ctx)
return snapshot, release, nil
}

func (s *snapshot) WriteEnv(ctx context.Context, w io.Writer) error {
s.view.optionsMu.Lock()
env := s.view.options.EnvSlice()
Expand Down Expand Up @@ -1057,6 +1048,10 @@ func (v *View) SetVulnerabilities(modfile span.URI, vulns []command.Vuln) {
v.vulns[modfile] = vulns
}

func (v *View) GoVersion() int {
return v.workspaceInformation.goversion
}

// Copied from
// https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/str/path.go;l=58;drc=2910c5b4a01a573ebc97744890a07c1a3122c67a
func globsMatchPath(globs, target string) bool {
Expand Down
29 changes: 29 additions & 0 deletions gopls/internal/lsp/general.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ func (s *Server) initialized(ctx context.Context, params *protocol.InitializedPa
return err
}
s.pendingFolders = nil
s.checkViewGoVersions()

var registrations []protocol.Registration
if options.ConfigurationSupported && options.DynamicConfigurationSupported {
Expand All @@ -223,6 +224,34 @@ func (s *Server) initialized(ctx context.Context, params *protocol.InitializedPa
return nil
}

// OldestSupportedGoVersion is the last X in Go 1.X that we support.
//
// Mutable for testing, since we won't otherwise run CI on unsupported Go
// versions.
var OldestSupportedGoVersion = 16

// checkViewGoVersions checks whether any Go version used by a view is too old,
// raising a showMessage notification if so.
//
// It should be called after views change.
func (s *Server) checkViewGoVersions() {
oldestVersion := -1
for _, view := range s.session.Views() {
viewVersion := view.GoVersion()
if oldestVersion == -1 || viewVersion < oldestVersion {
oldestVersion = viewVersion
}
}

if oldestVersion >= 0 && oldestVersion < OldestSupportedGoVersion {
msg := fmt.Sprintf("Found Go version 1.%d, which is unsupported. Please upgrade to Go 1.%d or later.", oldestVersion, OldestSupportedGoVersion)
s.eventuallyShowMessage(context.Background(), &protocol.ShowMessageParams{
Type: protocol.Error,
Message: msg,
})
}
}

func (s *Server) addFolders(ctx context.Context, folders []protocol.WorkspaceFolder) error {
originalViews := len(s.session.Views())
viewErrors := make(map[span.URI]error)
Expand Down
14 changes: 8 additions & 6 deletions gopls/internal/lsp/regtest/expectation.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,17 +162,19 @@ func NoOutstandingWork() SimpleExpectation {
}
}

// NoShowMessage asserts that the editor has not received a ShowMessage.
func NoShowMessage() SimpleExpectation {
// NoShownMessage asserts that the editor has not received a ShowMessage.
func NoShownMessage(subString string) SimpleExpectation {
check := func(s State) Verdict {
if len(s.showMessage) == 0 {
return Met
for _, m := range s.showMessage {
if strings.Contains(m.Message, subString) {
return Unmeetable
}
}
return Unmeetable
return Met
}
return SimpleExpectation{
check: check,
description: "no ShowMessage received",
description: fmt.Sprintf("no ShowMessage received containing %q", subString),
}
}

Expand Down
8 changes: 3 additions & 5 deletions gopls/internal/lsp/source/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,11 +253,6 @@ type View interface {
// no longer needed.
Snapshot(ctx context.Context) (Snapshot, func())

// Rebuild rebuilds the current view, replacing the original
// view in its session. It returns a Snapshot and a release
// function that must be called when the Snapshot is no longer needed.
Rebuild(ctx context.Context) (Snapshot, func(), error)

// IsGoPrivatePath reports whether target is a private import path, as identified
// by the GOPRIVATE environment variable.
IsGoPrivatePath(path string) bool
Expand All @@ -284,6 +279,9 @@ type View interface {

// FileKind returns the type of a file
FileKind(FileHandle) FileKind

// GoVersion returns the configured Go version for this view.
GoVersion() int
}

// A FileSource maps uris to FileHandles. This abstraction exists both for
Expand Down
3 changes: 3 additions & 0 deletions gopls/internal/lsp/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ func (s *Server) didChangeConfiguration(ctx context.Context, _ *protocol.DidChan
}()
}

// An options change may have affected the detected Go version.
s.checkViewGoVersions()

registration := semanticTokenRegistration(options.SemanticTypes, options.SemanticMods)
// Update any session-specific registrations or unregistrations.
if !semanticTokensRegistered && options.SemanticTokens {
Expand Down
11 changes: 10 additions & 1 deletion gopls/internal/regtest/diagnostics/diagnostics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,10 @@ func _() {
}

func TestEnableAllExperiments(t *testing.T) {
// Before the oldest supported Go version, gopls sends a warning to upgrade
// Go, which fails the expectation below.
testenv.NeedsGo1Point(t, lsp.OldestSupportedGoVersion)

const mod = `
-- go.mod --
module mod.com
Expand All @@ -1374,7 +1378,12 @@ func b(c bytes.Buffer) {
Settings{"allExperiments": true},
).Run(t, mod, func(t *testing.T, env *Env) {
// Confirm that the setting doesn't cause any warnings.
env.Await(NoShowMessage())
env.Await(
OnceMet(
InitialWorkspaceLoad,
NoShownMessage(""), // empty substring to match any message
),
)
})
}

Expand Down
77 changes: 77 additions & 0 deletions gopls/internal/regtest/workspace/workspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@
package workspace

import (
"context"
"fmt"
"path/filepath"
"strings"
"testing"

"golang.org/x/tools/gopls/internal/hooks"
"golang.org/x/tools/gopls/internal/lsp"
"golang.org/x/tools/gopls/internal/lsp/fake"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/internal/bug"
"golang.org/x/tools/internal/gocommand"
"golang.org/x/tools/internal/testenv"

. "golang.org/x/tools/gopls/internal/lsp/regtest"
Expand Down Expand Up @@ -1234,3 +1237,77 @@ import (
)
})
}

// Test that we don't get a version warning when the Go version in PATH is
// supported.
func TestOldGoNotification_SupportedVersion(t *testing.T) {
v := goVersion(t)
if v < lsp.OldestSupportedGoVersion {
t.Skipf("go version 1.%d is unsupported", v)
}

Run(t, "", func(t *testing.T, env *Env) {
env.Await(
OnceMet(
InitialWorkspaceLoad,
NoShownMessage("upgrade"),
),
)
})
}

// Test that we do get a version warning when the Go version in PATH is
// unsupported, though this test may never execute if we stop running CI at
// legacy Go versions (see also TestOldGoNotification_Fake)
func TestOldGoNotification_UnsupportedVersion(t *testing.T) {
v := goVersion(t)
if v >= lsp.OldestSupportedGoVersion {
t.Skipf("go version 1.%d is supported", v)
}

Run(t, "", func(t *testing.T, env *Env) {
env.Await(
OnceMet(
InitialWorkspaceLoad,
ShownMessage("Please upgrade"),
),
)
})
}

func TestOldGoNotification_Fake(t *testing.T) {
// Get the Go version from path, and make sure it's unsupported.
//
// In the future we'll stop running CI on legacy Go versions. By mutating the
// oldest supported Go version here, we can at least ensure that the
// ShowMessage pop-up works.
ctx := context.Background()
goversion, err := gocommand.GoVersion(ctx, gocommand.Invocation{}, &gocommand.Runner{})
if err != nil {
t.Fatal(err)
}
defer func(v int) {
lsp.OldestSupportedGoVersion = v
}(lsp.OldestSupportedGoVersion)
lsp.OldestSupportedGoVersion = goversion + 1

Run(t, "", func(t *testing.T, env *Env) {
env.Await(
OnceMet(
InitialWorkspaceLoad,
ShownMessage("Please upgrade"),
),
)
})
}

// goVersion returns the version of the Go command in PATH.
func goVersion(t *testing.T) int {
t.Helper()
ctx := context.Background()
goversion, err := gocommand.GoVersion(ctx, gocommand.Invocation{}, &gocommand.Runner{})
if err != nil {
t.Fatal(err)
}
return goversion
}

0 comments on commit 20c1ee7

Please sign in to comment.