github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/source/completion/definition.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package completion 6 7 import ( 8 "go/ast" 9 "go/token" 10 "go/types" 11 "strings" 12 "unicode" 13 "unicode/utf8" 14 15 "github.com/powerman/golang-tools/internal/lsp/protocol" 16 "github.com/powerman/golang-tools/internal/lsp/snippet" 17 "github.com/powerman/golang-tools/internal/lsp/source" 18 ) 19 20 // some definitions can be completed 21 // So far, TestFoo(t *testing.T), TestMain(m *testing.M) 22 // BenchmarkFoo(b *testing.B), FuzzFoo(f *testing.F) 23 24 // path[0] is known to be *ast.Ident 25 func definition(path []ast.Node, obj types.Object, fset *token.FileSet, mapper *protocol.ColumnMapper, fh source.FileHandle) ([]CompletionItem, *Selection) { 26 if _, ok := obj.(*types.Func); !ok { 27 return nil, nil // not a function at all 28 } 29 if !strings.HasSuffix(fh.URI().Filename(), "_test.go") { 30 return nil, nil 31 } 32 33 name := path[0].(*ast.Ident).Name 34 if len(name) == 0 { 35 // can't happen 36 return nil, nil 37 } 38 pos := path[0].Pos() 39 sel := &Selection{ 40 content: "", 41 cursor: pos, 42 MappedRange: source.NewMappedRange(fset, mapper, pos, pos), 43 } 44 var ans []CompletionItem 45 46 // Always suggest TestMain, if possible 47 if strings.HasPrefix("TestMain", name) { 48 ans = []CompletionItem{defItem("TestMain(m *testing.M)", obj)} 49 } 50 51 // If a snippet is possible, suggest it 52 if strings.HasPrefix("Test", name) { 53 ans = append(ans, defSnippet("Test", "Xxx", "(t *testing.T)", obj)) 54 return ans, sel 55 } else if strings.HasPrefix("Benchmark", name) { 56 ans = append(ans, defSnippet("Benchmark", "Xxx", "(b *testing.B)", obj)) 57 return ans, sel 58 } else if strings.HasPrefix("Fuzz", name) { 59 ans = append(ans, defSnippet("Fuzz", "Xxx", "(f *testing.F)", obj)) 60 return ans, sel 61 } 62 63 // Fill in the argument for what the user has already typed 64 if got := defMatches(name, "Test", path, "(t *testing.T)"); got != "" { 65 ans = append(ans, defItem(got, obj)) 66 } else if got := defMatches(name, "Benchmark", path, "(b *testing.B)"); got != "" { 67 ans = append(ans, defItem(got, obj)) 68 } else if got := defMatches(name, "Fuzz", path, "(f *testing.F)"); got != "" { 69 ans = append(ans, defItem(got, obj)) 70 } 71 return ans, sel 72 } 73 74 func defMatches(name, pat string, path []ast.Node, arg string) string { 75 idx := strings.Index(name, pat) 76 if idx < 0 { 77 return "" 78 } 79 c, _ := utf8.DecodeRuneInString(name[len(pat):]) 80 if unicode.IsLower(c) { 81 return "" 82 } 83 fd, ok := path[1].(*ast.FuncDecl) 84 if !ok { 85 // we don't know what's going on 86 return "" 87 } 88 fp := fd.Type.Params 89 if fp != nil && len(fp.List) > 0 { 90 // signature already there, minimal suggestion 91 return name 92 } 93 // suggesting signature too 94 return name + arg 95 } 96 97 func defSnippet(prefix, placeholder, suffix string, obj types.Object) CompletionItem { 98 var sn snippet.Builder 99 sn.WriteText(prefix) 100 if placeholder != "" { 101 sn.WritePlaceholder(func(b *snippet.Builder) { b.WriteText(placeholder) }) 102 } 103 sn.WriteText(suffix + " {\n") 104 sn.WriteFinalTabstop() 105 sn.WriteText("\n}") 106 return CompletionItem{ 107 Label: prefix + placeholder + suffix, 108 Detail: "tab, type the rest of the name, then tab", 109 Kind: protocol.FunctionCompletion, 110 Depth: 0, 111 Score: 10, 112 snippet: &sn, 113 Documentation: prefix + " test function", 114 obj: obj, 115 } 116 } 117 func defItem(val string, obj types.Object) CompletionItem { 118 return CompletionItem{ 119 Label: val, 120 InsertText: val, 121 Kind: protocol.FunctionCompletion, 122 Depth: 0, 123 Score: 9, // prefer the snippets when available 124 Documentation: "complete the parameter", 125 obj: obj, 126 } 127 }