// 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 completion import ( "go/ast" "go/token" "go/types" "strings" "unicode" "unicode/utf8" "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/snippet" "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/span" ) // some definitions can be completed // So far, TestFoo(t *testing.T), TestMain(m *testing.M) // BenchmarkFoo(b *testing.B), FuzzFoo(f *testing.F) // path[0] is known to be *ast.Ident func definition(path []ast.Node, obj types.Object, fset *token.FileSet, fh source.FileHandle) ([]CompletionItem, *Selection) { if _, ok := obj.(*types.Func); !ok { return nil, nil // not a function at all } if !strings.HasSuffix(fh.URI().Filename(), "_test.go") { return nil, nil } name := path[0].(*ast.Ident).Name if len(name) == 0 { // can't happen return nil, nil } pos := path[0].Pos() sel := &Selection{ content: "", cursor: pos, rng: span.NewRange(fset, pos, pos), } var ans []CompletionItem // Always suggest TestMain, if possible if strings.HasPrefix("TestMain", name) { ans = []CompletionItem{defItem("TestMain(m *testing.M)", obj)} } // If a snippet is possible, suggest it if strings.HasPrefix("Test", name) { ans = append(ans, defSnippet("Test", "Xxx", "(t *testing.T)", obj)) return ans, sel } else if strings.HasPrefix("Benchmark", name) { ans = append(ans, defSnippet("Benchmark", "Xxx", "(b *testing.B)", obj)) return ans, sel } else if strings.HasPrefix("Fuzz", name) { ans = append(ans, defSnippet("Fuzz", "Xxx", "(f *testing.F)", obj)) return ans, sel } // Fill in the argument for what the user has already typed if got := defMatches(name, "Test", path, "(t *testing.T)"); got != "" { ans = append(ans, defItem(got, obj)) } else if got := defMatches(name, "Benchmark", path, "(b *testing.B)"); got != "" { ans = append(ans, defItem(got, obj)) } else if got := defMatches(name, "Fuzz", path, "(f *testing.F)"); got != "" { ans = append(ans, defItem(got, obj)) } return ans, sel } func defMatches(name, pat string, path []ast.Node, arg string) string { idx := strings.Index(name, pat) if idx < 0 { return "" } c, _ := utf8.DecodeRuneInString(name[len(pat):]) if unicode.IsLower(c) { return "" } fd, ok := path[1].(*ast.FuncDecl) if !ok { // we don't know what's going on return "" } fp := fd.Type.Params if fp != nil && len(fp.List) > 0 { // signature already there, minimal suggestion return name } // suggesting signature too return name + arg } func defSnippet(prefix, placeholder, suffix string, obj types.Object) CompletionItem { var sn snippet.Builder sn.WriteText(prefix) if placeholder != "" { sn.WritePlaceholder(func(b *snippet.Builder) { b.WriteText(placeholder) }) } sn.WriteText(suffix + " {\n") sn.WriteFinalTabstop() sn.WriteText("\n}") return CompletionItem{ Label: prefix + placeholder + suffix, Detail: "tab, type the rest of the name, then tab", Kind: protocol.FunctionCompletion, Depth: 0, Score: 10, snippet: &sn, Documentation: prefix + " test function", obj: obj, } } func defItem(val string, obj types.Object) CompletionItem { return CompletionItem{ Label: val, InsertText: val, Kind: protocol.FunctionCompletion, Depth: 0, Score: 9, // prefer the snippets when available Documentation: "complete the parameter", obj: obj, } }