blob: c9ce2f029246d55466b26640243e7a7df25fa169 [file] [log] [blame]
// 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 workspace
import (
"sort"
"testing"
"github.com/google/go-cmp/cmp"
"golang.org/x/tools/gopls/internal/lsp/protocol"
. "golang.org/x/tools/gopls/internal/lsp/regtest"
)
func TestStandaloneFiles(t *testing.T) {
const files = `
-- go.mod --
module mod.test
go 1.16
-- lib/lib.go --
package lib
const K = 0
type I interface {
M()
}
-- lib/ignore.go --
//go:build ignore
// +build ignore
package main
import (
"mod.test/lib"
)
const K = 1
type Mer struct{}
func (Mer) M()
func main() {
println(lib.K + K)
}
`
WithOptions(
// On Go 1.17 and earlier, this test fails with
// experimentalWorkspaceModule. Not investigated, as
// experimentalWorkspaceModule will be removed.
Modes(Default),
).Run(t, files, func(t *testing.T, env *Env) {
// Initially, gopls should not know about the standalone file as it hasn't
// been opened. Therefore, we should only find one symbol 'K'.
//
// (The choice of "K" is a little sleazy: it was originally "C" until
// we started adding "unsafe" to the workspace unconditionally, which
// caused a spurious match of "unsafe.Slice". But in practice every
// workspace depends on unsafe.)
syms := env.Symbol("K")
if got, want := len(syms), 1; got != want {
t.Errorf("got %d symbols, want %d (%+v)", got, want, syms)
}
// Similarly, we should only find one reference to "K", and no
// implementations of I.
checkLocations := func(method string, gotLocations []protocol.Location, wantFiles ...string) {
var gotFiles []string
for _, l := range gotLocations {
gotFiles = append(gotFiles, env.Sandbox.Workdir.URIToPath(l.URI))
}
sort.Strings(gotFiles)
sort.Strings(wantFiles)
if diff := cmp.Diff(wantFiles, gotFiles); diff != "" {
t.Errorf("%s(...): unexpected locations (-want +got):\n%s", method, diff)
}
}
env.OpenFile("lib/lib.go")
env.AfterChange(NoDiagnostics())
// Replacing K with D should not cause any workspace diagnostics, since we
// haven't yet opened the standalone file.
env.RegexpReplace("lib/lib.go", "K", "D")
env.AfterChange(NoDiagnostics())
env.RegexpReplace("lib/lib.go", "D", "K")
env.AfterChange(NoDiagnostics())
refs := env.References(env.RegexpSearch("lib/lib.go", "K"))
checkLocations("References", refs, "lib/lib.go")
impls := env.Implementations(env.RegexpSearch("lib/lib.go", "I"))
checkLocations("Implementations", impls) // no implementations
// Opening the standalone file should not result in any diagnostics.
env.OpenFile("lib/ignore.go")
env.AfterChange(NoDiagnostics())
// Having opened the standalone file, we should find its symbols in the
// workspace.
syms = env.Symbol("K")
if got, want := len(syms), 2; got != want {
t.Fatalf("got %d symbols, want %d", got, want)
}
foundMainK := false
var symNames []string
for _, sym := range syms {
symNames = append(symNames, sym.Name)
if sym.Name == "main.K" {
foundMainK = true
}
}
if !foundMainK {
t.Errorf("WorkspaceSymbol(\"K\") = %v, want containing main.K", symNames)
}
// We should resolve workspace definitions in the standalone file.
fileLoc := env.GoToDefinition(env.RegexpSearch("lib/ignore.go", "lib.(K)"))
file := env.Sandbox.Workdir.URIToPath(fileLoc.URI)
if got, want := file, "lib/lib.go"; got != want {
t.Errorf("GoToDefinition(lib.K) = %v, want %v", got, want)
}
// ...as well as intra-file definitions
loc := env.GoToDefinition(env.RegexpSearch("lib/ignore.go", "\\+ (K)"))
wantLoc := env.RegexpSearch("lib/ignore.go", "const (K)")
if loc != wantLoc {
t.Errorf("GoToDefinition(K) = %v, want %v", loc, wantLoc)
}
// Renaming "lib.K" to "lib.D" should cause a diagnostic in the standalone
// file.
env.RegexpReplace("lib/lib.go", "K", "D")
env.AfterChange(Diagnostics(env.AtRegexp("lib/ignore.go", "lib.(K)")))
// Undoing the replacement should fix diagnostics
env.RegexpReplace("lib/lib.go", "D", "K")
env.AfterChange(NoDiagnostics())
// Now that our workspace has no errors, we should be able to find
// references and rename.
refs = env.References(env.RegexpSearch("lib/lib.go", "K"))
checkLocations("References", refs, "lib/lib.go", "lib/ignore.go")
impls = env.Implementations(env.RegexpSearch("lib/lib.go", "I"))
checkLocations("Implementations", impls, "lib/ignore.go")
// Renaming should rename in the standalone package.
env.Rename(env.RegexpSearch("lib/lib.go", "K"), "D")
env.RegexpSearch("lib/ignore.go", "lib.D")
})
}
func TestStandaloneFiles_Configuration(t *testing.T) {
const files = `
-- go.mod --
module mod.test
go 1.18
-- lib.go --
package lib // without this package, files are loaded as command-line-arguments
-- ignore.go --
//go:build ignore
// +build ignore
package main
// An arbitrary comment.
func main() {}
-- standalone.go --
//go:build standalone
// +build standalone
package main
func main() {}
`
WithOptions(
Settings{
"standaloneTags": []string{"standalone", "script"},
},
).Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("ignore.go")
env.OpenFile("standalone.go")
env.AfterChange(
Diagnostics(env.AtRegexp("ignore.go", "package (main)")),
NoDiagnostics(ForFile("standalone.go")),
)
cfg := env.Editor.Config()
cfg.Settings = map[string]interface{}{
"standaloneTags": []string{"ignore"},
}
env.ChangeConfiguration(cfg)
// TODO(golang/go#56158): gopls does not purge previously published
// diagnostice when configuration changes.
env.RegexpReplace("ignore.go", "arbitrary", "meaningless")
env.AfterChange(
NoDiagnostics(ForFile("ignore.go")),
Diagnostics(env.AtRegexp("standalone.go", "package (main)")),
)
})
}