diff --git a/go/packages/packagestest/expect.go b/go/packages/packagestest/expect.go index 2ba785ed..6b78f2c9 100644 --- a/go/packages/packagestest/expect.go +++ b/go/packages/packagestest/expect.go @@ -254,6 +254,23 @@ func (e *Exported) buildConverter(pt reflect.Type) (converter, error) { } return reflect.ValueOf(b), args, nil }, nil + case pt.Kind() == reflect.Slice: + return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) { + converter, err := e.buildConverter(pt.Elem()) + if err != nil { + return reflect.Value{}, nil, err + } + result := reflect.MakeSlice(reflect.SliceOf(pt.Elem()), 0, len(args)) + for range args { + value, remains, err := converter(n, args) + if err != nil { + return reflect.Value{}, nil, err + } + result = reflect.Append(result, value) + args = remains + } + return result, args, nil + }, nil default: return nil, fmt.Errorf("param has invalid type %v", pt) } diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go index 878793b4..583822d8 100644 --- a/internal/lsp/completion.go +++ b/internal/lsp/completion.go @@ -1,3 +1,7 @@ +// Copyright 2018 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 lsp import ( diff --git a/internal/lsp/diagnostics_test.go b/internal/lsp/diagnostics_test.go deleted file mode 100644 index 5ac7018a..00000000 --- a/internal/lsp/diagnostics_test.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2018 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 lsp - -import ( - "go/token" - "path/filepath" - "reflect" - "sort" - "strings" - "testing" - - "golang.org/x/tools/go/packages" - "golang.org/x/tools/go/packages/packagestest" - "golang.org/x/tools/internal/lsp/protocol" - "golang.org/x/tools/internal/lsp/source" -) - -func TestDiagnostics(t *testing.T) { - packagestest.TestAll(t, testDiagnostics) -} - -func testDiagnostics(t *testing.T, exporter packagestest.Exporter) { - files := packagestest.MustCopyFileTree("testdata/diagnostics") - // TODO(rstambler): Stop hardcoding this if we have more files that don't parse. - files["noparse/noparse.go"] = packagestest.Copy("testdata/diagnostics/noparse/noparse.go.in") - modules := []packagestest.Module{ - { - Name: "golang.org/x/tools/internal/lsp", - Files: files, - }, - } - exported := packagestest.Export(t, exporter, modules) - defer exported.Cleanup() - - dirs := make(map[string]bool) - wants := make(map[string][]protocol.Diagnostic) - for _, module := range modules { - for fragment := range module.Files { - if !strings.HasSuffix(fragment, ".go") { - continue - } - filename := exporter.Filename(exported, module.Name, fragment) - wants[filename] = []protocol.Diagnostic{} - dirs[filepath.Dir(filename)] = true - } - } - err := exported.Expect(map[string]interface{}{ - "diag": func(pos token.Position, msg string) { - line := float64(pos.Line - 1) - col := float64(pos.Column - 1) - want := protocol.Diagnostic{ - Range: protocol.Range{ - Start: protocol.Position{ - Line: line, - Character: col, - }, - End: protocol.Position{ - Line: line, - Character: col, - }, - }, - Severity: protocol.SeverityError, - Source: "LSP: Go compiler", - Message: msg, - } - if _, ok := wants[pos.Filename]; ok { - wants[pos.Filename] = append(wants[pos.Filename], want) - } else { - t.Errorf("unexpected filename: %v", pos.Filename) - } - }, - }) - if err != nil { - t.Fatal(err) - } - 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) - } - v := source.NewView() - // merge the config objects - cfg := *exported.Config - cfg.Fset = v.Config.Fset - cfg.Mode = packages.LoadSyntax - v.Config = &cfg - for _, pkg := range pkgs { - for _, filename := range pkg.GoFiles { - diagnostics, err := diagnostics(v, source.ToURI(filename)) - if err != nil { - t.Fatal(err) - } - got := diagnostics[filename] - sort.Slice(got, func(i int, j int) bool { - return got[i].Range.Start.Line < got[j].Range.Start.Line - }) - want := wants[filename] - if equal := reflect.DeepEqual(want, got); !equal { - t.Errorf("diagnostics failed for %s: (expected: %v), (got: %v)", filename, want, got) - } - } - } -} diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go new file mode 100644 index 00000000..ee1377e3 --- /dev/null +++ b/internal/lsp/lsp_test.go @@ -0,0 +1,201 @@ +// Copyright 2018 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 lsp + +import ( + "context" + "go/token" + "io/ioutil" + "path/filepath" + "reflect" + "sort" + "strings" + "testing" + + "golang.org/x/tools/go/packages" + "golang.org/x/tools/go/packages/packagestest" + "golang.org/x/tools/internal/lsp/protocol" + "golang.org/x/tools/internal/lsp/source" +) + +func TestLSP(t *testing.T) { + packagestest.TestAll(t, testLSP) +} + +func testLSP(t *testing.T, exporter packagestest.Exporter) { + dir := "testdata" + files := packagestest.MustCopyFileTree(dir) + subdirs, err := ioutil.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + for _, subdir := range subdirs { + if !subdir.IsDir() { + continue + } + dirpath := filepath.Join(dir, subdir.Name()) + if testFiles, err := ioutil.ReadDir(dirpath); err == nil { + for _, file := range testFiles { + if trimmed := strings.TrimSuffix(file.Name(), ".in"); trimmed != file.Name() { + files[filepath.Join(subdir.Name(), trimmed)] = packagestest.Copy(filepath.Join(dirpath, file.Name())) + } + } + } + } + modules := []packagestest.Module{ + { + Name: "golang.org/x/tools/internal/lsp", + Files: files, + }, + } + exported := packagestest.Export(t, exporter, modules) + defer exported.Cleanup() + + dirs := make(map[string]bool) + + // collect results for certain tests + expectedDiagnostics := make(map[string][]protocol.Diagnostic) + expectedCompletions := make(map[token.Position]*protocol.CompletionItem) + + s := &server{ + view: source.NewView(), + } + // merge the config objects + cfg := *exported.Config + cfg.Fset = s.view.Config.Fset + 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 + } + } + // Collect any data that needs to be used by subsequent tests. + if err := exported.Expect(map[string]interface{}{ + "diag": func(pos token.Position, msg string) { + line := float64(pos.Line - 1) + col := float64(pos.Column - 1) + want := protocol.Diagnostic{ + Range: protocol.Range{ + Start: protocol.Position{ + Line: line, + Character: col, + }, + End: protocol.Position{ + Line: line, + Character: col, + }, + }, + Severity: protocol.SeverityError, + Source: "LSP: Go compiler", + Message: msg, + } + if _, ok := expectedDiagnostics[pos.Filename]; ok { + expectedDiagnostics[pos.Filename] = append(expectedDiagnostics[pos.Filename], want) + } else { + t.Errorf("unexpected filename: %v", pos.Filename) + } + }, + "item": func(pos token.Position, label, detail, kind string) { + var k protocol.CompletionItemKind + switch kind { + case "struct": + k = protocol.StructCompletion + case "func": + k = protocol.FunctionCompletion + case "var": + k = protocol.VariableCompletion + case "type": + k = protocol.TypeParameterCompletion + case "field": + k = protocol.FieldCompletion + case "interface": + k = protocol.InterfaceCompletion + case "const": + k = protocol.ConstantCompletion + case "method": + k = protocol.MethodCompletion + } + expectedCompletions[pos] = &protocol.CompletionItem{ + Label: label, + Detail: detail, + Kind: float64(k), + } + }, + }); err != nil { + t.Fatal(err) + } + + // test completion + testCompletion(t, exported, s, expectedCompletions) + + // test 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) + } + testDiagnostics(t, s.view, pkgs, expectedDiagnostics) +} + +func testDiagnostics(t *testing.T, v *source.View, pkgs []*packages.Package, wants map[string][]protocol.Diagnostic) { + for _, pkg := range pkgs { + for _, filename := range pkg.GoFiles { + diagnostics, err := diagnostics(v, source.ToURI(filename)) + if err != nil { + t.Fatal(err) + } + got := diagnostics[filename] + sort.Slice(got, func(i int, j int) bool { + return got[i].Range.Start.Line < got[j].Range.Start.Line + }) + want := wants[filename] + if equal := reflect.DeepEqual(want, got); !equal { + t.Errorf("diagnostics failed for %s: (expected: %v), (got: %v)", filepath.Base(filename), want, got) + } + } + } +} + +func testCompletion(t *testing.T, exported *packagestest.Exported, s *server, wants map[token.Position]*protocol.CompletionItem) { + if err := exported.Expect(map[string]interface{}{ + "complete": func(src token.Position, expected []token.Position) { + var want []protocol.CompletionItem + for _, pos := range expected { + want = append(want, *wants[pos]) + } + list, err := s.Completion(context.Background(), &protocol.CompletionParams{ + TextDocumentPositionParams: protocol.TextDocumentPositionParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: protocol.DocumentURI(source.ToURI(src.Filename)), + }, + Position: protocol.Position{ + Line: float64(src.Line - 1), + Character: float64(src.Column - 1), + }, + }, + }) + if err != nil { + t.Fatal(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) + } + }, + }); err != nil { + t.Fatal(err) + } +} diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go index 5d1e29ae..af7131fc 100644 --- a/internal/lsp/source/completion.go +++ b/internal/lsp/source/completion.go @@ -90,6 +90,9 @@ func completions(file *ast.File, pos token.Pos, fset *token.FileSet, pkg *types. if typ != nil && matchingTypes(typ, obj.Type()) { weight *= 10 } + if !strings.HasPrefix(obj.Name(), prefix) { + return items + } item := formatCompletion(obj, pkgStringer, weight, func(v *types.Var) bool { return isParameter(sig, v) }) @@ -203,7 +206,7 @@ func lexical(path []ast.Node, pos token.Pos, pkg *types.Package, info *types.Inf } scopes = append(scopes, info.Scopes[n]) } - scopes = append(scopes, pkg.Scope(), types.Universe) + scopes = append(scopes, pkg.Scope()) // Process scopes innermost first. for i, scope := range scopes { diff --git a/internal/lsp/source/file.go b/internal/lsp/source/file.go index 1400925e..8b9ddb2a 100644 --- a/internal/lsp/source/file.go +++ b/internal/lsp/source/file.go @@ -8,8 +8,9 @@ import ( "fmt" "go/ast" "go/token" - "golang.org/x/tools/go/packages" "io/ioutil" + + "golang.org/x/tools/go/packages" ) // File holds all the information we know about a file. diff --git a/internal/lsp/testdata/diagnostics/bad/bad.go b/internal/lsp/testdata/bad/bad.go similarity index 100% rename from internal/lsp/testdata/diagnostics/bad/bad.go rename to internal/lsp/testdata/bad/bad.go diff --git a/internal/lsp/testdata/diagnostics/bad/bad_util.go b/internal/lsp/testdata/bad/bad_util.go similarity index 100% rename from internal/lsp/testdata/diagnostics/bad/bad_util.go rename to internal/lsp/testdata/bad/bad_util.go diff --git a/internal/lsp/testdata/diagnostics/bar/bar.go b/internal/lsp/testdata/bar/bar.go similarity index 100% rename from internal/lsp/testdata/diagnostics/bar/bar.go rename to internal/lsp/testdata/bar/bar.go diff --git a/internal/lsp/testdata/diagnostics/baz/baz.go b/internal/lsp/testdata/baz/baz.go similarity index 100% rename from internal/lsp/testdata/diagnostics/baz/baz.go rename to internal/lsp/testdata/baz/baz.go diff --git a/internal/lsp/testdata/diagnostics/foo/foo.go b/internal/lsp/testdata/diagnostics/foo/foo.go deleted file mode 100644 index 18be5d37..00000000 --- a/internal/lsp/testdata/diagnostics/foo/foo.go +++ /dev/null @@ -1,3 +0,0 @@ -package foo - -func Foo() {} diff --git a/internal/lsp/testdata/foo/foo.go b/internal/lsp/testdata/foo/foo.go new file mode 100644 index 00000000..bb92eb1a --- /dev/null +++ b/internal/lsp/testdata/foo/foo.go @@ -0,0 +1,23 @@ +package foo + +type StructFoo struct { //@mark(StructFoo, "StructFoo"),item(StructFoo, "StructFoo", "struct{...}", "struct") + Value int //@mark(Value, "Value"),item(Value, "Value", "int", "field") +} + +// TODO(rstambler): Create pre-set builtins? +//@mark(Error, ""),item(Error, "Error()", "string", "method") + +func Foo() { //@mark(Foo, "Foo"),item(Foo, "Foo()", "", "func") + var err error + err.Error() //@complete("E", Error) +} + +func _() { + var sFoo StructFoo //@complete("t", StructFoo) + if x := sFoo; x.Value == 1 { //@complete("V", Value) + return + } +} + +//@complete("", Foo, IntFoo, StructFoo) +type IntFoo int //@mark(IntFoo, "IntFoo"),item(IntFoo, "IntFoo", "int", "type") diff --git a/internal/lsp/testdata/diagnostics/good/good.go b/internal/lsp/testdata/good/good.go similarity index 100% rename from internal/lsp/testdata/diagnostics/good/good.go rename to internal/lsp/testdata/good/good.go diff --git a/internal/lsp/testdata/diagnostics/good/good_util.go b/internal/lsp/testdata/good/good_util.go similarity index 100% rename from internal/lsp/testdata/diagnostics/good/good_util.go rename to internal/lsp/testdata/good/good_util.go diff --git a/internal/lsp/testdata/diagnostics/noparse/noparse.go.in b/internal/lsp/testdata/noparse/noparse.go.in similarity index 100% rename from internal/lsp/testdata/diagnostics/noparse/noparse.go.in rename to internal/lsp/testdata/noparse/noparse.go.in