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  }