| // 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)")), |
| ) |
| }) |
| } |