| // 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 C = 0 |
| |
| type I interface { |
| M() |
| } |
| -- lib/ignore.go -- |
| //go:build ignore |
| // +build ignore |
| |
| package main |
| |
| import ( |
| "mod.test/lib" |
| ) |
| |
| const C = 1 |
| |
| type Mer struct{} |
| func (Mer) M() |
| |
| func main() { |
| println(lib.C + C) |
| } |
| ` |
| 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 'C'. |
| syms := env.WorkspaceSymbol("C") |
| if got, want := len(syms), 1; got != want { |
| t.Errorf("got %d symbols, want %d", got, want) |
| } |
| |
| // Similarly, we should only find one reference to "C", 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.Await( |
| OnceMet( |
| env.DoneWithOpen(), |
| NoOutstandingDiagnostics(), |
| ), |
| ) |
| |
| // Replacing C with D should not cause any workspace diagnostics, since we |
| // haven't yet opened the standalone file. |
| env.RegexpReplace("lib/lib.go", "C", "D") |
| env.Await( |
| OnceMet( |
| env.DoneWithChange(), |
| NoOutstandingDiagnostics(), |
| ), |
| ) |
| env.RegexpReplace("lib/lib.go", "D", "C") |
| env.Await( |
| OnceMet( |
| env.DoneWithChange(), |
| NoOutstandingDiagnostics(), |
| ), |
| ) |
| |
| refs := env.References("lib/lib.go", env.RegexpSearch("lib/lib.go", "C")) |
| checkLocations("References", refs, "lib/lib.go") |
| |
| impls := env.Implementations("lib/lib.go", 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.Await( |
| OnceMet( |
| env.DoneWithOpen(), |
| NoOutstandingDiagnostics(), |
| ), |
| ) |
| |
| // Having opened the standalone file, we should find its symbols in the |
| // workspace. |
| syms = env.WorkspaceSymbol("C") |
| if got, want := len(syms), 2; got != want { |
| t.Fatalf("got %d symbols, want %d", got, want) |
| } |
| |
| foundMainC := false |
| var symNames []string |
| for _, sym := range syms { |
| symNames = append(symNames, sym.Name) |
| if sym.Name == "main.C" { |
| foundMainC = true |
| } |
| } |
| if !foundMainC { |
| t.Errorf("WorkspaceSymbol(\"C\") = %v, want containing main.C", symNames) |
| } |
| |
| // We should resolve workspace definitions in the standalone file. |
| file, _ := env.GoToDefinition("lib/ignore.go", env.RegexpSearch("lib/ignore.go", "lib.(C)")) |
| if got, want := file, "lib/lib.go"; got != want { |
| t.Errorf("GoToDefinition(lib.C) = %v, want %v", got, want) |
| } |
| |
| // ...as well as intra-file definitions |
| file, pos := env.GoToDefinition("lib/ignore.go", env.RegexpSearch("lib/ignore.go", "\\+ (C)")) |
| if got, want := file, "lib/ignore.go"; got != want { |
| t.Errorf("GoToDefinition(C) = %v, want %v", got, want) |
| } |
| wantPos := env.RegexpSearch("lib/ignore.go", "const (C)") |
| if pos != wantPos { |
| t.Errorf("GoToDefinition(C) = %v, want %v", pos, wantPos) |
| } |
| |
| // Renaming "lib.C" to "lib.D" should cause a diagnostic in the standalone |
| // file. |
| env.RegexpReplace("lib/lib.go", "C", "D") |
| env.Await( |
| OnceMet( |
| env.DoneWithChange(), |
| env.DiagnosticAtRegexp("lib/ignore.go", "lib.(C)"), |
| ), |
| ) |
| |
| // Undoing the replacement should fix diagnostics |
| env.RegexpReplace("lib/lib.go", "D", "C") |
| env.Await( |
| OnceMet( |
| env.DoneWithChange(), |
| NoOutstandingDiagnostics(), |
| ), |
| ) |
| |
| // Now that our workspace has no errors, we should be able to find |
| // references and rename. |
| refs = env.References("lib/lib.go", env.RegexpSearch("lib/lib.go", "C")) |
| checkLocations("References", refs, "lib/lib.go", "lib/ignore.go") |
| |
| impls = env.Implementations("lib/lib.go", env.RegexpSearch("lib/lib.go", "I")) |
| checkLocations("Implementations", impls, "lib/ignore.go") |
| |
| // Renaming should rename in the standalone package. |
| env.Rename("lib/lib.go", env.RegexpSearch("lib/lib.go", "C"), "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.Await( |
| OnceMet( |
| env.DoneWithOpen(), |
| env.DiagnosticAtRegexp("ignore.go", "package (main)"), |
| NoDiagnostics("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.Await( |
| OnceMet( |
| env.DoneWithChange(), |
| NoDiagnostics("ignore.go"), |
| env.DiagnosticAtRegexp("standalone.go", "package (main)"), |
| ), |
| ) |
| }) |
| } |