golang.org/x/tools/gopls@v0.15.3/internal/golang/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/types" 10 "strings" 11 "unicode" 12 "unicode/utf8" 13 14 "golang.org/x/tools/gopls/internal/golang" 15 "golang.org/x/tools/gopls/internal/golang/completion/snippet" 16 "golang.org/x/tools/gopls/internal/protocol" 17 ) 18 19 // some function definitions in test files can be completed 20 // So far, TestFoo(t *testing.T), TestMain(m *testing.M) 21 // BenchmarkFoo(b *testing.B), FuzzFoo(f *testing.F) 22 23 // path[0] is known to be *ast.Ident 24 func definition(path []ast.Node, obj types.Object, pgf *golang.ParsedGoFile) ([]CompletionItem, *Selection) { 25 if _, ok := obj.(*types.Func); !ok { 26 return nil, nil // not a function at all 27 } 28 if !strings.HasSuffix(pgf.URI.Path(), "_test.go") { 29 return nil, nil // not a test file 30 } 31 32 name := path[0].(*ast.Ident).Name 33 if len(name) == 0 { 34 // can't happen 35 return nil, nil 36 } 37 start := path[0].Pos() 38 end := path[0].End() 39 sel := &Selection{ 40 content: "", 41 cursor: start, 42 tokFile: pgf.Tok, 43 start: start, 44 end: end, 45 mapper: pgf.Mapper, 46 } 47 var ans []CompletionItem 48 var hasParens bool 49 n, ok := path[1].(*ast.FuncDecl) 50 if !ok { 51 return nil, nil // can't happen 52 } 53 if n.Recv != nil { 54 return nil, nil // a method, not a function 55 } 56 t := n.Type.Params 57 if t.Closing != t.Opening { 58 hasParens = true 59 } 60 61 // Always suggest TestMain, if possible 62 if strings.HasPrefix("TestMain", name) { 63 if hasParens { 64 ans = append(ans, defItem("TestMain", obj)) 65 } else { 66 ans = append(ans, defItem("TestMain(m *testing.M)", obj)) 67 } 68 } 69 70 // If a snippet is possible, suggest it 71 if strings.HasPrefix("Test", name) { 72 if hasParens { 73 ans = append(ans, defItem("Test", obj)) 74 } else { 75 ans = append(ans, defSnippet("Test", "(t *testing.T)", obj)) 76 } 77 return ans, sel 78 } else if strings.HasPrefix("Benchmark", name) { 79 if hasParens { 80 ans = append(ans, defItem("Benchmark", obj)) 81 } else { 82 ans = append(ans, defSnippet("Benchmark", "(b *testing.B)", obj)) 83 } 84 return ans, sel 85 } else if strings.HasPrefix("Fuzz", name) { 86 if hasParens { 87 ans = append(ans, defItem("Fuzz", obj)) 88 } else { 89 ans = append(ans, defSnippet("Fuzz", "(f *testing.F)", obj)) 90 } 91 return ans, sel 92 } 93 94 // Fill in the argument for what the user has already typed 95 if got := defMatches(name, "Test", path, "(t *testing.T)"); got != "" { 96 ans = append(ans, defItem(got, obj)) 97 } else if got := defMatches(name, "Benchmark", path, "(b *testing.B)"); got != "" { 98 ans = append(ans, defItem(got, obj)) 99 } else if got := defMatches(name, "Fuzz", path, "(f *testing.F)"); got != "" { 100 ans = append(ans, defItem(got, obj)) 101 } 102 return ans, sel 103 } 104 105 // defMatches returns text for defItem, never for defSnippet 106 func defMatches(name, pat string, path []ast.Node, arg string) string { 107 if !strings.HasPrefix(name, pat) { 108 return "" 109 } 110 c, _ := utf8.DecodeRuneInString(name[len(pat):]) 111 if unicode.IsLower(c) { 112 return "" 113 } 114 fd, ok := path[1].(*ast.FuncDecl) 115 if !ok { 116 // we don't know what's going on 117 return "" 118 } 119 fp := fd.Type.Params 120 if len(fp.List) > 0 { 121 // signature already there, nothing to suggest 122 return "" 123 } 124 if fp.Opening != fp.Closing { 125 // nothing: completion works on words, not easy to insert arg 126 return "" 127 } 128 // suggesting signature too 129 return name + arg 130 } 131 132 func defSnippet(prefix, suffix string, obj types.Object) CompletionItem { 133 var sn snippet.Builder 134 sn.WriteText(prefix) 135 sn.WritePlaceholder(func(b *snippet.Builder) { b.WriteText("Xxx") }) 136 sn.WriteText(suffix + " {\n\t") 137 sn.WriteFinalTabstop() 138 sn.WriteText("\n}") 139 return CompletionItem{ 140 Label: prefix + "Xxx" + suffix, 141 Detail: "tab, type the rest of the name, then tab", 142 Kind: protocol.FunctionCompletion, 143 Depth: 0, 144 Score: 10, 145 snippet: &sn, 146 Documentation: prefix + " test function", 147 isSlice: isSlice(obj), 148 } 149 } 150 func defItem(val string, obj types.Object) CompletionItem { 151 return CompletionItem{ 152 Label: val, 153 InsertText: val, 154 Kind: protocol.FunctionCompletion, 155 Depth: 0, 156 Score: 9, // prefer the snippets when available 157 Documentation: "complete the function name", 158 isSlice: isSlice(obj), 159 } 160 }