1
0
mirror of https://github.com/golang/go synced 2024-11-18 15:04:44 -07:00

internal/lsp: add more testdata for completion and diagnostics

Change-Id: I2a73e51b60f76a2af0f8ff4d34220b551e0cd378
Reviewed-on: https://go-review.googlesource.com/c/150041
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
Rebecca Stambler 2018-11-13 11:13:53 -05:00
parent 89e258047f
commit fc4f04983f
19 changed files with 378 additions and 113 deletions

View File

@ -7,11 +7,12 @@ package packagestest
import (
"fmt"
"go/token"
"path/filepath"
"reflect"
"regexp"
"strings"
"golang.org/x/tools/go/expect"
"golang.org/x/tools/go/packages"
)
const (
@ -131,11 +132,18 @@ func (e *Exported) getNotes() error {
return nil
}
notes := []*expect.Note{}
var dirs []string
for _, module := range e.written {
for _, filename := range module {
if !strings.HasSuffix(filename, ".go") {
continue
}
dirs = append(dirs, filepath.Dir(filename))
}
}
pkgs, err := packages.Load(e.Config, dirs...)
if err != nil {
return fmt.Errorf("unable to load packages for directories %s: %v", dirs, err)
}
for _, pkg := range pkgs {
for _, filename := range pkg.GoFiles {
l, err := expect.Parse(e.fset, filename, nil)
if err != nil {
return fmt.Errorf("Failed to extract expectations: %v", err)

View File

@ -5,6 +5,8 @@
package lsp
import (
"sort"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
)
@ -36,3 +38,15 @@ func toProtocolSeverity(severity source.DiagnosticSeverity) protocol.DiagnosticS
}
return protocol.SeverityError // default
}
func sorted(d []protocol.Diagnostic) {
sort.Slice(d, func(i int, j int) bool {
if d[i].Range.Start.Line == d[j].Range.Start.Line {
if d[i].Range.Start.Character == d[j].Range.End.Character {
return d[i].Message < d[j].Message
}
return d[i].Range.Start.Character < d[j].Range.End.Character
}
return d[i].Range.Start.Line < d[j].Range.Start.Line
})
}

View File

@ -0,0 +1,7 @@
//+build !go1.11
package lsp
func init() {
goVersion111 = false
}

View File

@ -12,7 +12,6 @@ import (
"os/exec"
"path/filepath"
"reflect"
"sort"
"strings"
"testing"
@ -22,14 +21,18 @@ import (
"golang.org/x/tools/internal/lsp/source"
)
// TODO(rstambler): Remove this once Go 1.12 is released as we will end support
// for versions of Go <= 1.10.
var goVersion111 = true
func TestLSP(t *testing.T) {
packagestest.TestAll(t, testLSP)
}
func testLSP(t *testing.T, exporter packagestest.Exporter) {
const dir = "testdata"
const expectedCompletionsCount = 4
const expectedDiagnosticsCount = 9
const expectedCompletionsCount = 43
const expectedDiagnosticsCount = 14
const expectedFormatCount = 3
const expectedDefinitionsCount = 16
@ -49,8 +52,6 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
exported := packagestest.Export(t, exporter, modules)
defer exported.Cleanup()
dirs := make(map[string]bool)
// collect results for certain tests
expectedDiagnostics := make(diagnostics)
completionItems := make(completionItems)
@ -67,16 +68,6 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
cfg.Mode = packages.LoadSyntax
s.view.Config = &cfg
for _, module := range modules {
for fragment := range module.Files {
if !strings.HasSuffix(fragment, ".go") {
continue
}
filename := exporter.Filename(exported, module.Name, fragment)
expectedDiagnostics[filename] = []protocol.Diagnostic{}
dirs[filepath.Dir(filename)] = true
}
}
// Do a first pass to collect special markers
if err := exported.Expect(map[string]interface{}{
"item": func(name string, r packagestest.Range, _, _ string) {
@ -98,32 +89,40 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
t.Run("Completion", func(t *testing.T) {
t.Helper()
if len(expectedCompletions) != expectedCompletionsCount {
t.Errorf("got %v completions expected %v", len(expectedCompletions), expectedCompletionsCount)
if goVersion111 { // TODO(rstambler): Remove this when we no longer support Go 1.10.
if len(expectedCompletions) != expectedCompletionsCount {
t.Errorf("got %v completions expected %v", len(expectedCompletions), expectedCompletionsCount)
}
}
expectedCompletions.test(t, exported, s, completionItems)
})
t.Run("Diagnostics", func(t *testing.T) {
t.Helper()
diagnosticsCount := expectedDiagnostics.test(t, exported, s.view, dirs)
if diagnosticsCount != expectedDiagnosticsCount {
t.Errorf("got %v diagnostics expected %v", diagnosticsCount, expectedDiagnosticsCount)
diagnosticsCount := expectedDiagnostics.test(t, exported, s.view)
if goVersion111 { // TODO(rstambler): Remove this when we no longer support Go 1.10.
if diagnosticsCount != expectedDiagnosticsCount {
t.Errorf("got %v diagnostics expected %v", diagnosticsCount, expectedDiagnosticsCount)
}
}
})
t.Run("Format", func(t *testing.T) {
t.Helper()
if len(expectedFormat) != expectedFormatCount {
t.Errorf("got %v formats expected %v", len(expectedFormat), expectedFormatCount)
if goVersion111 { // TODO(rstambler): Remove this when we no longer support Go 1.10.
if len(expectedFormat) != expectedFormatCount {
t.Errorf("got %v formats expected %v", len(expectedFormat), expectedFormatCount)
}
}
expectedFormat.test(t, s)
})
t.Run("Definitions", func(t *testing.T) {
t.Helper()
if len(expectedDefinitions) != expectedDefinitionsCount {
t.Errorf("got %v definitions expected %v", len(expectedDefinitions), expectedDefinitionsCount)
if goVersion111 { // TODO(rstambler): Remove this when we no longer support Go 1.10.
if len(expectedDefinitions) != expectedDefinitionsCount {
t.Errorf("got %v definitions expected %v", len(expectedDefinitions), expectedDefinitionsCount)
}
}
expectedDefinitions.test(t, s)
})
@ -153,11 +152,11 @@ func (c completions) test(t *testing.T, exported *packagestest.Exported, s *serv
},
})
if err != nil {
t.Fatal(err)
t.Fatalf("completion failed for %s:%v:%v: %v", filepath.Base(src.Filename), src.Line, src.Column, err)
}
got := list.Items
if equal := reflect.DeepEqual(want, got); !equal {
t.Errorf("completion failed for %s:%v:%v: (expected: %v), (got: %v)", filepath.Base(src.Filename), src.Line, src.Column, want, got)
t.Errorf(diffC(src, want, got))
}
}
}
@ -185,6 +184,8 @@ func (i completionItems) collect(pos token.Pos, label, detail, kind string) {
k = protocol.ConstantCompletion
case "method":
k = protocol.MethodCompletion
case "package":
k = protocol.ModuleCompletion
}
i[pos] = &protocol.CompletionItem{
Label: label,
@ -193,50 +194,33 @@ func (i completionItems) collect(pos token.Pos, label, detail, kind string) {
}
}
func (d diagnostics) test(t *testing.T, exported *packagestest.Exported, v *source.View, dirs map[string]bool) int {
// first trigger a load to get the diagnostics
var dirList []string
for dir := range dirs {
dirList = append(dirList, dir)
}
exported.Config.Mode = packages.LoadFiles
pkgs, err := packages.Load(exported.Config, dirList...)
if err != nil {
t.Fatal(err)
}
// and now see if they match the expected ones
func (d diagnostics) test(t *testing.T, exported *packagestest.Exported, v *source.View) int {
count := 0
for _, pkg := range pkgs {
for _, filename := range pkg.GoFiles {
f := v.GetFile(source.ToURI(filename))
diagnostics, err := source.Diagnostics(context.Background(), v, f)
if err != nil {
t.Fatal(err)
}
got := toProtocolDiagnostics(v, diagnostics[filename])
sort.Slice(got, func(i int, j int) bool {
return got[i].Range.Start.Line < got[j].Range.Start.Line
})
want := d[filename]
if equal := reflect.DeepEqual(want, got); !equal {
msg := &bytes.Buffer{}
fmt.Fprintf(msg, "diagnostics failed for %s: expected:\n", filepath.Base(filename))
for _, d := range want {
fmt.Fprintf(msg, " %v\n", d)
}
fmt.Fprintf(msg, "got:\n")
for _, d := range got {
fmt.Fprintf(msg, " %v\n", d)
}
t.Error(msg.String())
}
count += len(want)
for filename, want := range d {
f := v.GetFile(source.ToURI(filename))
sourceDiagnostics, err := source.Diagnostics(context.Background(), v, f)
if err != nil {
t.Fatal(err)
}
got := toProtocolDiagnostics(v, sourceDiagnostics[filename])
sorted(got)
if equal := reflect.DeepEqual(want, got); !equal {
t.Error(diffD(filename, want, got))
}
count += len(want)
}
return count
}
func (d diagnostics) collect(pos token.Position, msg string) {
if _, ok := d[pos.Filename]; !ok {
d[pos.Filename] = []protocol.Diagnostic{}
}
// If a file has an empty diagnostics, mark that and return. This allows us
// to avoid testing diagnostics in files that may have a lot of them.
if msg == "" {
return
}
line := float64(pos.Line - 1)
col := float64(pos.Column - 1)
want := protocol.Diagnostic{
@ -268,7 +252,7 @@ func (f formats) test(t *testing.T, s *server) {
if gofmted != "" {
t.Error(err)
}
return
continue
}
edit := edits[0]
if edit.NewText != gofmted {
@ -312,3 +296,31 @@ func (d definitions) collect(fset *token.FileSet, src, target packagestest.Range
tLoc := toProtocolLocation(fset, tRange)
d[sLoc] = tLoc
}
// diffD prints the diff between expected and actual diagnostics test results.
func diffD(filename string, want, got []protocol.Diagnostic) string {
msg := &bytes.Buffer{}
fmt.Fprintf(msg, "diagnostics failed for %s:\nexpected:\n", filename)
for _, d := range want {
fmt.Fprintf(msg, " %v\n", d)
}
fmt.Fprintf(msg, "got:\n")
for _, d := range got {
fmt.Fprintf(msg, " %v\n", d)
}
return msg.String()
}
// diffC prints the diff between expected and actual completion test results.
func diffC(pos token.Position, want, got []protocol.CompletionItem) string {
msg := &bytes.Buffer{}
fmt.Fprintf(msg, "completion failed for %s:%v:%v:\nexpected:\n", filepath.Base(pos.Filename), pos.Line, pos.Column)
for _, d := range want {
fmt.Fprintf(msg, " %v\n", d)
}
fmt.Fprintf(msg, "got:\n")
for _, d := range got {
fmt.Fprintf(msg, " %v\n", d)
}
return msg.String()
}

View File

@ -54,3 +54,30 @@ func (s DiagnosticSeverity) Format(f fmt.State, c rune) {
func (d Diagnostic) Format(f fmt.State, c rune) {
fmt.Fprintf(f, "%v:%v from %v at %v: %v", d.Severity, d.Code, d.Source, d.Range, d.Message)
}
func (i CompletionItem) Format(f fmt.State, c rune) {
fmt.Fprintf(f, "%v %v %v", i.Label, i.Detail, CompletionItemKind(i.Kind))
}
func (k CompletionItemKind) Format(f fmt.State, c rune) {
switch k {
case StructCompletion:
fmt.Fprintf(f, "struct")
case FunctionCompletion:
fmt.Fprintf(f, "func")
case VariableCompletion:
fmt.Fprintf(f, "var")
case TypeParameterCompletion:
fmt.Fprintf(f, "type")
case FieldCompletion:
fmt.Fprintf(f, "field")
case InterfaceCompletion:
fmt.Fprintf(f, "interface")
case ConstantCompletion:
fmt.Fprintf(f, "const")
case MethodCompletion:
fmt.Fprintf(f, "method")
case ModuleCompletion:
fmt.Fprintf(f, "package")
}
}

View File

@ -15,7 +15,7 @@ import (
type CompletionItem struct {
Label, Detail string
Kind CompletionItemKind
Score int
Score float64
}
type CompletionItemKind int
@ -47,6 +47,8 @@ func Completion(ctx context.Context, f *File, pos token.Pos) ([]CompletionItem,
return items, err
}
const stdScore float64 = 1.0
type finder func(types.Object, float64, []CompletionItem) []CompletionItem
// completions returns the map of possible candidates for completion, given a
@ -89,7 +91,7 @@ func completions(file *ast.File, pos token.Pos, fset *token.FileSet, pkg *types.
if !seen[obj] {
seen[obj] = true
if typ != nil && matchingTypes(typ, obj.Type()) {
weight *= 10
weight *= 10.0
}
if !strings.HasPrefix(obj.Name(), prefix) {
return items
@ -160,7 +162,7 @@ func selector(sel *ast.SelectorExpr, info *types.Info, found finder) (items []Co
scope := pkgname.Imported().Scope()
// TODO testcase: bad import
for _, name := range scope.Names() {
items = found(scope.Lookup(name), 1, items)
items = found(scope.Lookup(name), stdScore, items)
}
return items, prefix, nil
}
@ -175,20 +177,20 @@ func selector(sel *ast.SelectorExpr, info *types.Info, found finder) (items []Co
// methods of T
mset := types.NewMethodSet(tv.Type)
for i := 0; i < mset.Len(); i++ {
items = found(mset.At(i).Obj(), 1, items)
items = found(mset.At(i).Obj(), stdScore, items)
}
// methods of *T
if tv.Addressable() && !types.IsInterface(tv.Type) && !isPointer(tv.Type) {
mset := types.NewMethodSet(types.NewPointer(tv.Type))
for i := 0; i < mset.Len(); i++ {
items = found(mset.At(i).Obj(), 1, items)
items = found(mset.At(i).Obj(), stdScore, items)
}
}
// fields of T
for _, f := range fieldSelections(tv.Type) {
items = found(f, 1, items)
items = found(f, stdScore, items)
}
return items, prefix, nil
@ -236,7 +238,7 @@ func lexical(path []ast.Node, pos token.Pos, pkg *types.Package, info *types.Inf
}
}
score := 1.0
score := stdScore
// Rank builtins significantly lower than other results.
if scope == types.Universe {
score *= 0.1
@ -342,7 +344,7 @@ func complit(path []ast.Node, pos token.Pos, pkg *types.Package, info *types.Inf
structPkg = field.Pkg()
}
if !addedFields[field] {
items = found(field, 10, items)
items = found(field, 10.0, items)
}
}
// Add lexical completions if the user hasn't typed a key value expression
@ -418,6 +420,7 @@ func formatCompletion(obj types.Object, qualifier types.Qualifier, score float64
Label: label,
Detail: detail,
Kind: kind,
Score: score,
}
}

View File

@ -0,0 +1,19 @@
package assign_rank
var (
apple int = 3 //@item(apple, "apple", "int", "var")
pear string = "hello" //@item(pear, "pear", "string", "var")
)
func _() {
orange := 1 //@item(orange, "orange", "int", "var")
grape := "hello" //@item(grape, "grape", "string", "var")
orange, grape = 2, "hello" //@complete(" \"", grape, pear, orange, apple)
}
func _() {
var pineapple int //@item(pineapple, "pineapple", "int", "var")
pineapple = //@complete(" /", pineapple, apple, pear)
y := //@complete(" /", pineapple, apple, pear)
}

View File

@ -1,18 +1,21 @@
// +build go1.11
package bad
func stuff() {
func stuff() { //@item(stuff, "stuff()", "", "func")
x := "heeeeyyyy"
random2(x) //@diag("x", "cannot use x (variable of type string) as int value in argument to random2")
random2(1)
y := 3 //@diag("y", "y declared but not used")
random2(1) //@complete("dom", random, random2, random3)
y := 3 //@diag("y", "y declared but not used")
}
type bob struct {
type bob struct { //@item(bob, "bob", "struct{...}", "struct")
x int
}
func _() {
var q int
_ = &bob{
f: 0, //@diag("f", "unknown field f in struct literal")
f: q, //@diag("f", "unknown field f in struct literal")
}
}

View File

@ -1,6 +1,27 @@
// +build go1.11
package bad
func random2(y int) int {
x := 6 //@diag("x", "x declared but not used")
// import (
// "github.com/bob/pkg" //@diag("\"github.com/bob/pkg\"", "unable to import "\"github.com/bob/pkg\"")
// )
var a unknown //@item(global_a, "a", "unknown", "var"),diag("unknown", "undeclared name: unknown")
func random() int { //@item(random, "random()", "int", "func")
//@complete("", global_a, bob, random, random2, random3, stuff)
return 0
}
func random2(y int) int { //@item(random2, "random2(y int)", "int", "func"),item(bad_y_param, "y", "int", "var")
x := 6 //@item(x, "x", "int", "var"),diag("x", "x declared but not used")
var q blah //@item(q, "q", "blah", "var"),diag("q", "q declared but not used"),diag("blah", "undeclared name: blah")
var t blob //@item(t, "t", "blob", "var"),diag("t", "t declared but not used"),diag("blob", "undeclared name: blob")
//@complete("", q, t, x, bad_y_param, global_a, bob, random, random2, random3, stuff)
return y
}
func random3(y ...int) { //@item(random3, "random3(y ...int)", "", "func"),item(y_variadic_param, "y", "[]int", "var")
//@complete("", y_variadic_param, global_a, bob, random, random2, random3, stuff)
}

View File

@ -1,7 +0,0 @@
package bar
import "golang.org/x/tools/internal/lsp/foo"
func Bar() {
foo.Foo()
}

43
internal/lsp/testdata/bar/bar.go.in vendored Normal file
View File

@ -0,0 +1,43 @@
// +build go1.11
package bar
import (
"golang.org/x/tools/internal/lsp/foo" //@item(foo, "foo", "\"golang.org/x/tools/internal/lsp/foo\"", "package")
)
func _() {
_ = foo.StructFoo{} //@complete("S", Foo, IntFoo, StructFoo)
}
func Bar() { //@item(Bar, "Bar()", "", "func")
foo.Foo() //@complete("F", Foo, IntFoo, StructFoo)
var _ foo.IntFoo //@complete("I", Foo, IntFoo, StructFoo)
foo.() //@complete("(", Foo, IntFoo, StructFoo)
}
func _() {
var Valentine int //@item(Valentine, "Valentine", "int", "var")
_ = foo.StructFoo{
Val //@complete("l", Value)
}
_ = foo.StructFoo{
Va //@complete("a", Value)
}
_ = foo.StructFoo{
Value: 5, //@complete("a", Value)
}
_ = foo.StructFoo{
//@complete("", Value)
}
_ = foo.StructFoo{
Value: Valen //@complete("le", Valentine)
}
_ = foo.StructFoo{
Value: //@complete(re"$", Valentine, foo, Bar)
}
_ = foo.StructFoo{
Value: //@complete(" ", Valentine, foo, Bar)
}
}

View File

@ -1,7 +0,0 @@
package baz
import "golang.org/x/tools/internal/lsp/bar"
func Baz() {
bar.Bar()
}

31
internal/lsp/testdata/baz/baz.go.in vendored Normal file
View File

@ -0,0 +1,31 @@
// +build go1.11
package baz
import (
"golang.org/x/tools/internal/lsp/bar"
f "golang.org/x/tools/internal/lsp/foo"
)
func Baz() {
defer bar.Bar() //@complete("B", Bar)
// TODO(rstambler): Test completion here.
defer bar.B
var _ f.IntFoo //@complete("n", IntFoo)
bar.Bar() //@complete("B", Bar)
}
func _() {
bob := f.StructFoo{Value: 5}
if x := bob. //@complete(re"$", Value)
switch true == false {
case true:
if x := bob. //@complete(re"$", Value)
case false:
}
if x := bob.Va //@complete("a", Value)
switch true == true {
default:
}
}

11
internal/lsp/testdata/cast/cast.go.in vendored Normal file
View File

@ -0,0 +1,11 @@
package cast
func _() {
foo := struct{x int}{x: 1} //@item(x_field, "x", "int", "field")
_ = float64(foo.x) //@complete("x", x_field)
}
func _() {
foo := struct{x int}{x: 1}
_ = float64(foo. //@complete(" /", x_field)
}

View File

@ -1,6 +1,6 @@
package good
package good //@diag("package", "")
func stuff() {
func stuff() { //@item(good_stuff, "stuff()", "", "func")
x := 5
random2(x)
}

View File

@ -1,10 +1,19 @@
package good
package good //@diag("package", "")
func random() int {
import (
"golang.org/x/tools/internal/lsp/types" //@item(types_import, "types", "\"golang.org/x/tools/internal/lsp/types\"", "package")
)
func random() int { //@item(good_random, "random()", "int", "func")
y := 6 + 7
return y
}
func random2(y int) int {
func random2(y int) int { //@item(good_random2, "random2(y int)", "int", "func"),item(good_y_param, "y", "int", "var")
//@complete("", good_y_param, types_import, good_random, good_random2, good_stuff)
var b types.Bob = &types.X{}
if _, ok := b.(*types.X); ok { //@complete("X", Bob_interface, X_struct, Y_struct)
}
return y
}

View File

@ -1,11 +0,0 @@
// +build !go1.11
// This file does not actually test anything
// on 1.10 the errors are different
package noparse_format
func what() {
// we need a diagnostic below so we have the same count as the main file
var b int //@diag("b", "b declared but not used")
if true {}
}

View File

@ -0,0 +1,66 @@
// +build go1.11
package selector
import (
"golang.org/x/tools/internal/lsp/bar"
)
type S struct {
B, A, C int //@item(Bf, "B", "int", "field"),item(Af, "A", "int", "field"),item(Cf, "C", "int", "field")
}
func _() {
_ = S{}.; //@complete(";", Bf, Af, Cf)
}
type bob struct { a int } //@item(a, "a", "int", "field")
type george struct { b int }
type jack struct { c int } //@item(c, "c", "int", "field")
type jill struct { d int }
func (b *bob) george() *george {} //@item(george, "george()", "*george", "method")
func (g *george) jack() *jack {}
func (j *jack) jill() *jill {} //@item(jill, "jill()", "*jill", "method")
func _() {
b := &bob{}
y := b.george().
jack();
y.; //@complete(";", jill, c)
}
func _() {
bar. //@complete(" /", Bar)
x := 5
var b *bob
b. //@complete(" /", george, a)
y, z := 5, 6
b. //@complete(" /", george, a)
y, z, a, b, c := 5, 6
}
func _() {
bar. //@complete(" /", Bar)
bar.Bar()
bar. //@complete(" /", Bar)
go f()
}
func _() {
var b *bob
if y != b. //@complete(" /", george, a)
z := 5
if z + y + 1 + b. //@complete(" /", george, a)
r, s, t := 4, 5
if y != b. //@complete(" /", george, a)
z = 5
if z + y + 1 + b. //@complete(" /", george, a)
r = 4
}

16
internal/lsp/testdata/types/types.go vendored Normal file
View File

@ -0,0 +1,16 @@
package types
type X struct { //@item(X_struct, "X", "struct{...}", "struct")
x int
}
type Y struct { //@item(Y_struct, "Y", "struct{...}", "struct")
y int
}
type Bob interface { //@item(Bob_interface, "Bob", "interface{...}", "interface")
Bobby()
}
func (*X) Bobby() {}
func (*Y) Bobby() {}