mirror of https://github.com/golang/go.git
129 lines
3.5 KiB
Go
129 lines
3.5 KiB
Go
// 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,
|
|
}
|
|
}
|