| // Copyright 2020 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 regtest |
| |
| import ( |
| "fmt" |
| "strings" |
| "testing" |
| |
| "golang.org/x/tools/internal/lsp" |
| "golang.org/x/tools/internal/lsp/fake" |
| "golang.org/x/tools/internal/lsp/protocol" |
| "golang.org/x/tools/internal/testenv" |
| ) |
| |
| func TestPackageCompletion(t *testing.T) { |
| testenv.NeedsGo1Point(t, 14) |
| const files = ` |
| -- go.mod -- |
| module mod.com |
| |
| go 1.12 |
| -- fruits/apple.go -- |
| package apple |
| |
| fun apple() int { |
| return 0 |
| } |
| |
| -- fruits/testfile.go -- |
| // this is a comment |
| |
| /* |
| this is a multiline comment |
| */ |
| |
| import "fmt" |
| |
| func test() {} |
| |
| -- fruits/testfile2.go -- |
| package |
| |
| -- fruits/testfile3.go -- |
| pac |
| ` |
| var ( |
| testfile4 = "" |
| testfile5 = "/*a comment*/ " |
| testfile6 = "/*a comment*/\n" |
| ) |
| for _, tc := range []struct { |
| name string |
| filename string |
| content *string |
| triggerRegexp string |
| want []string |
| editRegexp string |
| }{ |
| { |
| name: "package completion at valid position", |
| filename: "fruits/testfile.go", |
| triggerRegexp: "\n()", |
| want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, |
| editRegexp: "\n()", |
| }, |
| { |
| name: "package completion in a comment", |
| filename: "fruits/testfile.go", |
| triggerRegexp: "th(i)s", |
| want: nil, |
| }, |
| { |
| name: "package completion in a multiline comment", |
| filename: "fruits/testfile.go", |
| triggerRegexp: `\/\*\n()`, |
| want: nil, |
| }, |
| { |
| name: "package completion at invalid position", |
| filename: "fruits/testfile.go", |
| triggerRegexp: "import \"fmt\"\n()", |
| want: nil, |
| }, |
| { |
| name: "package completion after keyword 'package'", |
| filename: "fruits/testfile2.go", |
| triggerRegexp: "package()", |
| want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, |
| editRegexp: "package\n", |
| }, |
| { |
| name: "package completion with 'pac' prefix", |
| filename: "fruits/testfile3.go", |
| triggerRegexp: "pac()", |
| want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, |
| editRegexp: "pac", |
| }, |
| { |
| name: "package completion for empty file", |
| filename: "fruits/testfile4.go", |
| triggerRegexp: "^$", |
| content: &testfile4, |
| want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, |
| editRegexp: "^$", |
| }, |
| { |
| name: "package completion without terminal newline", |
| filename: "fruits/testfile5.go", |
| triggerRegexp: `\*\/ ()`, |
| content: &testfile5, |
| want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, |
| editRegexp: `\*\/ ()`, |
| }, |
| { |
| name: "package completion on terminal newline", |
| filename: "fruits/testfile6.go", |
| triggerRegexp: `\*\/\n()`, |
| content: &testfile6, |
| want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, |
| editRegexp: `\*\/\n()`, |
| }, |
| } { |
| t.Run(tc.name, func(t *testing.T) { |
| run(t, files, func(t *testing.T, env *Env) { |
| if tc.content != nil { |
| env.WriteWorkspaceFile(tc.filename, *tc.content) |
| env.Await( |
| CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1), |
| ) |
| } |
| env.OpenFile(tc.filename) |
| completions := env.Completion(tc.filename, env.RegexpSearch(tc.filename, tc.triggerRegexp)) |
| |
| // Check that the completion item suggestions are in the range |
| // of the file. |
| lineCount := len(strings.Split(env.Editor.BufferText(tc.filename), "\n")) |
| for _, item := range completions.Items { |
| if start := int(item.TextEdit.Range.Start.Line); start >= lineCount { |
| t.Fatalf("unexpected text edit range start line number: got %d, want less than %d", start, lineCount) |
| } |
| if end := int(item.TextEdit.Range.End.Line); end >= lineCount { |
| t.Fatalf("unexpected text edit range end line number: got %d, want less than %d", end, lineCount) |
| } |
| } |
| |
| if tc.want != nil { |
| start, end := env.RegexpRange(tc.filename, tc.editRegexp) |
| expectedRng := protocol.Range{ |
| Start: fake.Pos.ToProtocolPosition(start), |
| End: fake.Pos.ToProtocolPosition(end), |
| } |
| for _, item := range completions.Items { |
| gotRng := item.TextEdit.Range |
| if expectedRng != gotRng { |
| t.Errorf("unexpected completion range for completion item %s: got %v, want %v", |
| item.Label, gotRng, expectedRng) |
| } |
| } |
| } |
| |
| diff := compareCompletionResults(tc.want, completions.Items) |
| if diff != "" { |
| t.Error(diff) |
| } |
| }) |
| }) |
| } |
| } |
| |
| func TestPackageNameCompletion(t *testing.T) { |
| const files = ` |
| -- go.mod -- |
| module mod.com |
| |
| go 1.12 |
| -- math/add.go -- |
| package ma |
| ` |
| |
| want := []string{"ma", "ma_test", "main", "math", "math_test"} |
| run(t, files, func(t *testing.T, env *Env) { |
| env.OpenFile("math/add.go") |
| completions := env.Completion("math/add.go", fake.Pos{ |
| Line: 0, |
| Column: 10, |
| }) |
| |
| diff := compareCompletionResults(want, completions.Items) |
| if diff != "" { |
| t.Fatal(diff) |
| } |
| }) |
| } |
| |
| func compareCompletionResults(want []string, gotItems []protocol.CompletionItem) string { |
| if len(gotItems) != len(want) { |
| return fmt.Sprintf("got %v completion(s), want %v", len(gotItems), len(want)) |
| } |
| |
| var got []string |
| for _, item := range gotItems { |
| got = append(got, item.Label) |
| } |
| |
| for i, v := range got { |
| if v != want[i] { |
| return fmt.Sprintf("completion results are not the same: got %v, want %v", got, want) |
| } |
| } |
| |
| return "" |
| } |