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  }